什么是MVC
MVC是一种软件的构想将软 件按照模型、视图、控制器来划分
M:Model 模型, 指的是JavaBean 实体Bean和业务处理Bean(实体类和Dao、Service)
V:View 视图, 指Html或者JSP等页面
C:Controller 控制器,值工程中的Servlet,作用接收请求、响应请求
MVC工作流程:
用户通过视图发生请求到服务器,在服务器中被Controller接收,Controller调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller在根据请求处理的结果找到相应的View视图层,渲染数据后最终响应给游览器
2.什么是SpringMVC
SpringMVC是Spring的一个后续产品,是Spring的一个子模块,是Spring为表述层开发提供的一整套完备的解决方案
3.SpringMVC的特点
spring中要求配置文件位于recesouces下,则可以在web.xml的servlet配置中,配置init-param指定配置文件的位置,如果没有配置init-param,则需要在WEB-INF下创建名称为[servlet-name]-servlet.xml的配置文件
<!-- 1.配置控制器 DispatcherServlet--> <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 指定SpringMVC配置文件的位置 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springMVC.xml</param-value> </init-param> <!-- 指定当前servlet的启动顺序 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <!-- "/" 所有的请求(不包括.jsp),交由控制器DispatcherServlet去处理 --> <!-- "/*" 所有的请求(包括.jsp) --> <url-pattern>/</url-pattern> </servlet-mapping>
<!-- 1.组件加载--> <context:component-scan base-package="com.potato.controller"/> <!-- 2.静态资源处理 需要和 <mvc:annotation-driven /> 一起使用--> <mvc:default-servlet-handler /> <!-- 3.开启mvc注解驱动--> <mvc:annotation-driven /> <!-- 4.视图配置--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/static/html/" /> <property name="suffix" value=".jsp" /> </bean>
@Controller @RequestMapping("/test") public class HelloController { @RequestMapping("/index") public String index() { return "index"; } @RequestMapping("/hello") public String hello() { return "hello"; } }
在/WEB-INF/static/html/下创建index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>首页</title> </head> <body> <h1>首页</h1> <a href="${pageContext.request.contextPath}/hello">跳转</a> </body> </html>
配置启动Tomacat 即可完成web项目的访问
@Controller @RequestMapping("/test") public class HelloController { @RequestMapping("/index") //访问路径为 /test/index public String index() { return "index"; } @RequestMapping("/error") //访问路径为 /test/error public String error() { return "error"; } }
value: 通过请求路径匹配请求映射。可以是一个数组,表示该请求映射可以处理多个请求地址
method:通过请求方式匹配请求映射,请求方式 get/post/delete/put
params:通过请求参数匹配请求映射
headers:通过请求头匹配请求映射
@Controller @RequestMapping("/test") public class HelloController { // value // 可以处理 /test/index 和 /test/main 请求 @RequestMapping(value = {"index", "main"}) public String index() { return "index"; } // method // 只能处理 /test/index2 并且 请求方式为GET的请求 @RequestMapping(value = "index2", method = RequestMethod.GET) public String index2() { return "index"; } // params // 只能处理 /test/index3 并且 携带username参数,password参数的值必须是123456,age参数不能等于20,不允许有sex参数 @RequestMapping(value = "index3", params = {"username", "password=123456", "age!=20", "!sex"}) public String index3() { return "index"; } //headers // 只能处理 /test/index4 并且 请求头的Host值必须为localhost:8080 @RequestMapping(value = "index3", headers = {"Host=localhost:8080"}) public String index4() { return "index"; } }
“?” :表示任意单个字符
“*” :表示任意0个或多个字符
“**” :表示任意一层或多层目录
//注意:在使用 ”**“时,只能使用 /**/xxx 的形式 // 表示无论是 /aat/test 或者 /abt/test、/act/test 等等请求,都可以匹配到当前映射方法 @RequestMapping("/a?t/test") public String test() { return "index"; }
路径占位符常用于restful风格,在响应的@RequestMapping注解的value属性中通过占位符{xxx}表示要传输的数据,在通过@PathVarlable注解获取占位符中的值
/** 3. restful风格 */ //在@RequestMapping注解的value属性中通过占位符{xxx}表示要传输的数据,在通过@PathVarlable注解获取占位符中的值 @RequestMapping("/restful/{id}/{pageIndex}") public String restful(@PathVariable("id") String id,@PathVariable("pageIndex") Integer pageIndex) { System.out.println("id:"+id); System.out.println("pageIndex:"+pageIndex); return "index"; }
@GetMapping、@PostMapping、@DeleteMapping、@PutMapping
解决请求乱码问题
/** * 1.原生ServletAPI获取请求参数 */ @RequestMapping("index") public String index(HttpServletRequest request) { String username = request.getParameter("username"); String password = request.getParameter("password"); System.out.println("username:" + username); System.out.println("password:" + password); return "index"; } /** * 2.控制器方法形参获取请求参数,请求参数名和形参名 必须要一致 */ @RequestMapping("index2") public String index2(String username, String password) { System.out.println("username:" + username); System.out.println("password:" + password); return "index"; } /** * 3.@RequestParam, 请求参数名和形参名 不一致 * value/name: 请求参数的名称,name和value两个属性基本是等价的, * required:当前参数是否必须存在 * defaultValue:当前参数的默认值 */ @RequestMapping(value = "index3") public String index3(@RequestParam("user_name") String username) { System.out.println("username:" + username); return "index"; } /** * 4.@RequestHeader 获取请求头信息 * 和@RequestParam 一样的使用方法 */ @RequestMapping(value = "index4") public String index4(@RequestHeader("Host") String host) { System.out.println("host:" + host); return "index"; } /** * 5.@CookieValue 获取cookie数据 * 和@RequestParam 一样的使用方法 */ @RequestMapping(value = "index5") public String index5(@CookieValue("JSESSIONID") String JSESSIONID) { System.out.println("JSESSIONID:" + JSESSIONID); return "index"; } /** * 6.通过POJO获取请求参数 * 请求参数的名称和POJO类对象的属性名 要一致 */ @RequestMapping(value = "index6") public String index6(User user) { System.out.println(user); return "index"; }
get请求乱码 由tomcat照成,修改tomcat的配置文件
post乱码 配置CharacterEncodingFilter
<filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceResponseEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
//1.ServletAPI向request域对象共享数据 @RequestMapping("/testServletApi") public String testServletApi(HttpServletRequest request) { request.setAttribute("testRequestScope", "向域对象中共享的数据"); return "index"; } //2.ModelAndView向request域对象共享数据 @RequestMapping("/testModelAndView") public ModelAndView testModelAndView(ModelAndView mav) { // 向域对象中添加数据 mav.addObject("testRequestScope", "向域对象中共享的数据"); // 跳转的视图页面 mav.setViewName("index"); return mav; } //3.Model向request域对象共享数据 @RequestMapping("/testModel") public String testModel(Model model) { model.addAttribute("testRequestScope", "向域对象中共享的数据"); return "index"; } //4.Map向request域对象共享数据 @RequestMapping("/testMap") public String testMap(Map<String, Object> map) { map.put("testRequestScope", "向域对象中共享的数据"); return "index"; } //5.ModelMap向request域对象共享数据 @RequestMapping("/testModelMap") public String testModelMap(ModelMap modelMap) { modelMap.addAttribute("testRequestScope", "向域对象中共享的数据"); return "index"; } //6.Model、ModelMap、Map的关系 都是BindingAwareModelMap // public interface Model // public class ModelMap extends LinkedHashMap<String, Object> // public class ExtendedModelMap extends ModelMap implements Model // public class BindingAwareModelMap extends ExtendedModelMap //7.向session域对象共享数据 @RequestMapping("/testSessionScopeByServletApi") public String testSessionScopeByServletApi(HttpSession session) { session.setAttribute("testSessionScope", "向域对象中共享的数据"); return "index"; } //8.向application域对象共享数据 @RequestMapping("/testApplicationScopeByServletApi") public String testApplicationScopeByServletApi(HttpSession session) { ServletContext servletContext = session.getServletContext(); servletContext.setAttribute("testApplicationScope", "向域对象中共享的数据"); return "index"; }
@Controller @RequestMapping("/test") public class ViewController { @RequestMapping("/index") public String index() { return "index"; } // 转发视图,游览器发送了一次请求 请求地址不变 @RequestMapping("testForward") public String testForward() { return "forward:/test/index"; } // 重定向视图,游览器发送了两次请求 请求地址改变 @RequestMapping("testForward") public String testRedirect() { return "redirect:/test/index"; } }
<!-- 当访问路径仅仅只是为了跳转页面,可以通过配置 mvc:view-controller 但是配置此标签后,其他控制器将失效,因此还需要配置 <mvc:annotation-driven/> --> <mvc:view-controller path="/" view-name="index"/> <mvc:annotation-driven/>
HttpMessageConverter :将请求报文转换为java对象,或 将java对象转换为响应报文
HttpMessageConverter提供两个注解和两个类型:
@RequestBody,@ResponseBody,RequestEntity,ResponseEntity,@RestController
将请求报文(请求体)转换为java对象
// @RequestBody, @RequestMapping("form") public String request(@RequestBody String formData) { // 表单的数据以JSON字符串 的形式赋值给formData System.out.println(formData); return "success"; }
将java对象转换为响应报文(响应体)
// @ResponseBody //此处响应的"success" 不在作为视图进行解析,而是直接作为响应体返回给客户端 //如果响应的数据是一个java对象类型的 则需要导入json依赖,则返回的java对象自动转换为json对象 @ResponseBody @RequestMapping("response") public String response(){ return "success"; }
可以接受整个请求报文,包括请求头和请求体
// RequestEntity, @RequestMapping("request2") public String request2(RequestEntity<String> request) { // 获取请求头信息 HttpHeaders headers = request.getHeaders(); // 获取请求体信息 String body = request.getBody(); return "success"; }
ResponseEntity用于控制方法的返回值类型,该控制器方法的返回值就是响应到游览器的响应报文
(也就是自定义响应报文)
@RestController 替代 @Controller
表示当前控制器所有方法返回的结果 都是响应体,不是视图
通过ResponseEntity 进行文件下载
@RequestMapping("/testDown") public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException { //获取ServletContext对象 ServletContext servletContext = session.getServletContext(); //获取服务器中文件的真实路径 String realPath = servletContext.getRealPath("/static/img/1.jpg"); //创建输入流 InputStream is = new FileInputStream(realPath); //创建字节数组 byte[] bytes = new byte[is.available()]; //将流读到字节数组中 is.read(bytes); //创建HttpHeaders对象设置响应头信息 MultiValueMap<String, String> headers = new HttpHeaders(); //设置要下载方式以及下载文件的名字 headers.add("Content-Disposition", "attachment;filename=1.jpg"); //设置响应状态码 HttpStatus statusCode = HttpStatus.OK; //创建ResponseEntity对象 ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode); //关闭输入流 is.close(); return responseEntity; }
要求form表单的请求方式必须为post,并且添加属性enctype=“multipart/form-data”
SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息
上传步骤:
a>添加依赖
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency>
b>在SpringMVC的配置文件中添加配置:
<!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
c>控制器方法:
@RequestMapping("/testUp") public String testUp(MultipartFile photo, HttpSession session) throws IOException { //获取上传的文件的文件名 String fileName = photo.getOriginalFilename(); //处理文件重名问题 String hzName = fileName.substring(fileName.lastIndexOf(".")); fileName = UUID.randomUUID().toString() + hzName; //获取服务器中photo目录的路径 ServletContext servletContext = session.getServletContext(); String photoPath = servletContext.getRealPath("photo"); File file = new File(photoPath); if(!file.exists()){ file.mkdir(); } String finalPath = photoPath + File.separator + fileName; //实现上传功能 photo.transferTo(new File(finalPath)); return "success"; }
SpringMVC中的拦截器用于拦截控制器方法的执行
SpringMVC中的拦截器需要实现HandlerInterceptor接口,重写preHandle方法
public class FirstInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 拦截后进行逻辑处理的内容 返回true 代表放行,返回false 表示拦截 return HandlerInterceptor.super.preHandle(request, response, handler); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置:
<mvc:interceptors> <!-- 指定规则进行拦截--> <mvc:interceptor> <!-- 拦截所有请求但是不包括 "/test" 请求 /* 表示只有一层路径 /**表示任意多层路径--> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/test"/> <!-- 拦截请求后 进行处理的类--> <bean id="firstInterceptor" class="com.potato.interceptor.FirstInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
SpringMVC中的拦截器有三个抽象方法:
preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法
postHandle:控制器方法执行之后执行postHandle()
afterComplation:处理完视图和模型数据,渲染视图完毕之后执行afterComplation()
a>若每个拦截器的preHandle()都返回true
此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:
preHandle()会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行
b>若某个拦截器的preHandle()返回了false
preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterComplation()会执行
客户端–> 过滤器 --> DispatcherServlet --> 拦截器 -->Controller
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <!-- prop的键表示 需要进行处理的异常 prop的值表示 若出现异常时,需要跳转到的视图名称 --> <prop key="java.lang.ArithmeticException">error</prop> </props> </property> <!-- exceptionAttribute属性设置一个值,将异常信息在 请求域 中进行共享,通过ex可以获取异常的信息内容 --> <property name="exceptionAttribute" value="ex"/> </bean>
@ControllerAdvice public class ExceptionController { /** * @param ex 异常信息 * @param model 把异常信息添加到域对象中 * @return 异常页面 */ @ExceptionHandler(value = {ArithmeticException.class, NullPointerException.class}) public String exceptionMethod(Exception ex, Model model) { model.addAttribute(ex); return "error"; } }
创建初始化类,代替web.xml
在Servlet3.0+环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到的话就用它来配置Servlet容器。
Spring提供了这个接口的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,名为AbstractAnnotationConfigDispatcherServletInitializer,当我们的类扩展了AbstractAnnotationConfigDispatcherServletInitializer并将其部署到Servlet3.0容器的时候,容器会自动发现它,并用它来配置Servlet上下文。
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer { /** * 指定spring的配置类 * * @return */ @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringConfig.class}; } /** * 指定SpringMVC的配置类 * * @return */ @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{WebConfig.class}; } /** * 指定DispatcherServlet的映射规则,即url-pattern * * @return */ @Override protected String[] getServletMappings() { return new String[]{"/"}; } /** * 添加过滤器 * * @return */ @Override protected Filter[] getServletFilters() { CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); characterEncodingFilter.setEncoding("UTF-8"); characterEncodingFilter.setForceResponseEncoding(true); HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter(); return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter}; } }
代替SpringMVC的配置文件
// 1.组件扫描 2.视图解析 3.view-controller 4.default-servlet-handler // 5.mvc注解驱动 6.文件上传解析器 7.拦截器 8.异常处理 @Configuration // 1.组件扫描 @ComponentScan("com.potato.controller") // 5.mvc注解驱动 @EnableWebMvc public class WebConfig implements WebMvcConfigurer { //2.视图解析 @Override public void configureViewResolvers(ViewResolverRegistry registry) { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/static/"); viewResolver.setSuffix(".jsp"); registry.viewResolver(viewResolver); } //3.view-controller @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("index"); WebMvcConfigurer.super.addViewControllers(registry); } //4.default-servlet-handler @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); WebMvcConfigurer.super.configureDefaultServletHandling(configurer); } //6.文件上传解析器 public CommonsMultipartResolver multipartResolver(CommonsMultipartResolver commonsMultipartResolver) { commonsMultipartResolver.setDefaultEncoding("UTF-8"); commonsMultipartResolver.setMaxUploadSize(419430400); return commonsMultipartResolver; } //7.拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { // 注册自己定义的拦截器 //registry.addInterceptor(new FirstInterceptor()).addPathPatterns("/**"); WebMvcConfigurer.super.addInterceptors(registry); } //8.异常处理 可以使用注解配置代替 @Override public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) { SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver(); Properties prop = new Properties(); prop.setProperty("java.lang.ArithmeticException", "error"); //设置异常映射 resolver.setExceptionMappings(prop); //设置共享异常信息的键 resolver.setExceptionAttribute("ex"); resolvers.add(resolver); WebMvcConfigurer.super.configureHandlerExceptionResolvers(resolvers); } }
用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获。
DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:
根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。
DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。
如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】
提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。
此时将开始执行拦截器的postHandle(…)方法【逆向】。
根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。
渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。
将渲染结果返回给客户端。