SpringMVC框架使用详解

1、SpringMVC工作流程

1.1工作流程图

在这里插入图片描述

1.2代码运行流程

1、客户端点击链接发送http://xxx/x请求

2、来到tomcat服务器

3、SpringMVC的前端控制器 (DispatcherServlet) 收到所有请求,

4、来看请求地址的和@RequestMapping标注的哪个匹配,找到适合的处理器(Controller)进行处理

5、前端控制器找到了目标处理类和目标方法,使用反射执行方法(Controller中对应的方法),方法是SpringMVC调用的

6、方法执行完成以后会有一个返回值,判断是否加了@ResponseBody注解,如果加了此注解,就直接将内容返回

7、没加@ResponseBody注解,拿到返回值之后,使用视图解析器进行拼串得到完整的页面地址

8、拿到页面地址,前端控制器帮我们转发到页面

1.3DispatchServlet源码

1.3.1DispatcherServlet核心方法

请求的拦截核心调用的就是DispacherServlet类中的doDispatch()方法

  • doDispatch()方法中的handle()方法执行 Controller对应的方法

  • doDispatch()方法中的processDispatchResult()方法去页面

    大致的流程

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);

//2、根据当前请求地址找到哪个Controller可以处理 将这个Controller对象封装进        mappedHandler
         mappedHandler = getHandler(processedRequest);
          
         //3、没有 Controller可以处理请求就抛异常 直接结束
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

 
         //4、获取可以执行这个Controller的所有方法的适配器 (反射工具)
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

  	........
          
         // 5、使用适配器真正的执行目标方法,最终都会返回一个ModelAndView对象
         //即目标方法无论如何写,执行完毕之后的结果最后都会封装成一个ModelAndViewModelAndVie
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

 	........
       
       //6、根据方法最终执行完成后封装的ModelAndView,转发到对应的页面,并且ModelAndView中的数据可以在Request域中获取
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
 
       
}

1.3.2核心方法的作用及流程

请求过来,DispatcherServlet进行拦截,调用doDispatch()方法进行处理

1、调用getHandler()方法,根据当前的请求地址去HandlerMapping中找到这个请求的处理器(Controller)

2getHandlerAdapter()方法,根据当前的处理器(Controller)获取到能执行这个处理器方法的适配器

3、调用handle()方法,使用获取到的适配器(AnnotationMethodHandlerAdapter对象)执行目标方法,并返回一个ModelAndView对象(不管方法返回值是什么,最后都会封装为ModelAndView对象),里面封装了跳转的视图信息以及一些存的数据

4、根据ModelAndView的信息转发到具体的页面,并可以在请求域中取出ModelAndView中的数据

1.3.3getHandler()细节_去HandlerMapping找对应的处理器(Controller)

默认有两种HandlerMapping,一种是基于注解(DefaultAnnotationHandlerMapping),一种基于配置(Spring5中不在使用)(BeanNameUrlHandlerMapping),每一个处理器的方法(标注了@RequestMapping注解的方法)对应的处理器(Controller)全部保存在DefaultAnnotationHandlerMapping中的HandlerMap中,

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   if (this.handlerMappings != null) {
      //HandlerMapping中保存了每一个Controller(处理器)能处理哪些请求的信息
      //保存了 每一个方法对应的Controller类 (在handlerMap属性中)
      for (HandlerMapping mapping : this.handlerMappings) {
         HandlerExecutionChain handler = mapping.getHandler(request);
         if (handler != null) {
            return handler;
         }
      }
   }
   return null;
}

1.3.4getHandlerAdapter()方法,获取处理器(Controller)的可以执行方法的适配器

//传入的参数就是上一步获取的处理器类的对象即Controller对象
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
   if (this.handlerAdapters != null) {
       //最终获取的是AnnatationHandlerAdapter,
      for (HandlerAdapter adapter : this.handlerAdapters) {
         if (adapter.supports(handler)) {
            return adapter;
         }
      }
   }
   throw new ServletException("No adapter for handler [" + handler +
         "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

1.3.5handle()适配器执行目标方法

1.4SpringMVC九大组件及初始化

初始化:去容器中找这个组件,没有找到就用默认配置,我们使用的一般都是默认配置

共同点:九大组件全都是接口,接口就是规范!!!,我们可以自定义实现类进行扩展

//文件上传解析器
private MultipartResolver multipartResolver;

//区域信息解析器(跟国际化有关)
private LocaleResolver localeResolver;

//主题解析器
private ThemeResolver themeResolver;

//请求与处理器(Controller)的映射信息
private List<HandlerMapping> handlerMappings;

//处理器的适配器
private List<HandlerAdapter> handlerAdapters;

//处理器的异常解析器
private List<HandlerExceptionResolver> handlerExceptionResolvers;

//请求到视图的翻译器
private RequestToViewNameTranslator viewNameTranslator;

//SpringMVC允许重定向携带数据的功能
private FlashMapManager flashMapManager;

//视图解析器(前缀+后缀)
private List<ViewResolver> viewResolvers;

1.5视图解析器

2、核心

2.1@RequestMapping

2.1.2基本使用与概述

​ 在注解中写的路径是注解的value属性

  • 前面的 / 可加可不加,但是建议加上
//  /可写可不写,默认都是从当前项目开始 
@RequestMapping("/hello")
public String hello(){
    return "index";
}
  • 一个方法只能对应一个请求

  • 可以标注在类上和方法上,标在类上则为当前类中的所有方法全部都加上指定的路径

//方法和类上全都加入@RequestMapping注解 那么请求路径就是http://xxx:8080/okc/hello
@RequestMapping("/okc")
@Controller
public class UserController {
    
    @RequestMapping("/hello")
    public String hello(){
        return "index";
    }
}

2.1.3属性详解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String name() default "";
		
    //请求的路径
    @AliasFor("path")
    String[] value() default {};
	
  
    @AliasFor("value")
    String[] path() default {};
	
    //指定请求类型 一般都是Get或者Post 不指定默认所有请求都能处理
    RequestMethod[] method() default {};
	
    
 ---------------------------------------------------------------------------->   
    /*
    	规定请求参数
    	params={"k1","k2=v2","!k3","k4!=v4"}
    	表示:
    	你的url中必须全部满足下面的条件
    	        1、必须携带k1这个参数,值无限制
    			2、必须携带k2这个参数 且值必须是v2
    			3、不能携带k3这个参数
    			4、可以携带k4这个参数,也可以不带,如果带了的话值必须是v4
    */	
    String[] params() default {};
	
----------------------------------------------------------------------------->
    /*
    规定请求头,跟params的用法相同,可以规定请求头的内容
    headers={}
   
    */
    String[] headers() default {};

    
----------------------------------------------------------------------------->    	/*
		只接受内容类型是哪种的请求,规定请求头中的Content-Type
	*/
    String[] consumes() default {};

 --------------------------------------------------------------------------->   
     /*
		告诉浏览器返回的内容类型是什么,给响应头中加上Content-Type
		可以指定返回的是字符串是HTML代码,让浏览器解析
		produces=text/html
	*/
    String[] produces() default {};
}

2.1.4模糊匹配

匹配规则:精确优先
?只能匹配一个字符 0个多个都不行
*可以匹配多个字符
/*/匹配一层路径
/**/匹配多层路径
 
@RequestMapping(value = "/shwsh1")   A
@RequestMapping(value = "/shwsh?")   B
@RequestMapping(value = "/shwsh*")   C

访问路径为 /shwsh1 因为精确优先 A进行处理
访问路径为 /shwsh2 因为?匹配一个 比*精确 B进行处理
访问路径为 /shwsh123  只有C符合多个字符 C进行处理

----------------------------------------------------------------------------->

@RequestMapping(value = "/shwsh*/okc")       D
@RequestMapping(value = "/shwsh/*/okc")      E
@RequestMapping(value = "/shwsh/**/okc")     F

访问路径为 /shwsh32/okc          D处理   
访问路径为 /shwsh/asd/okc        E处理   /*/只能匹配一层路径
访问路径为 /shwsh/sd/sdds/okc    F处理   /**/可以匹配多层路径

2.1.5衍生注解@GetMapping

2.1.6衍生注解@PostMapping

2.2@PathVariable(获取路径上的参数值)

  • 路径上可以有占位符使用{xx},路径上的一个{xx}代表一层路径
  • 使用@PathVariable可以获取路径上占位符的值
@ResponseBody
@RequestMapping("/get/{id}/{name}")
public String zhanweifu(
    					//获取路径上的对应的值
                        @PathVariable("id")String id,
                        @PathVariable("name")String name){
        System.out.println(id+","+name);
        return id;
}

/get/1/  这个路径匹配不上 一个占位符代表一层路径

2.3@RequestParam(获取?后面的参数值)

2.3.1基本使用与注解详解

在原来的Web开发中,我们使用request.getParameter(“key”)获取参数值,比较的麻烦,SpringMVC为我们处理了处理参数的问题

一共有两种解决方式

第一种:保证参数名和我们的方法的参数名相同,SpringMVC自动封装
比如我们的URL = http://localhost:8080/test2?user=1
参数名user和我们的方法的参数名相同,SpringMVC帮我们获取参数值并封装到形参的user属性中 
    @RequestMapping(value = "/test2")
    public String tet2(String user){
        System.out.println(user);
        return user;
    }
	
第二种:使用@RequestParam注解,value属性指定为URL上的参数名,SpringMVC也能自动封装
    
    /*
    指定获取URL中?后的那个参数的值,写参数名
    String value() default "";
	
	该参数是否是必须携带的,默认是true,如果不携带此参数报404
    boolean required() default true;
	
	如果没有携带此参数,指定一个默认值,
    String defaultValue() default ""
    */
    
    @RequestMapping(value = "/test3")
    public String tet3(@RequestParam("username") String user){
        System.out.println(user);
        return user;
    }

2.3.2@PathVariable与@RequestParam的区别

@PathVariable 获取的是路径上的参数,代表的是一层路径 想要获取参数,在URL路径上必须使用{xx}表示出来
    
@RequestParam 获取的是?后面的参数   

http://localhost:8080/test4/{id}/{name}?user=1&age=123
    @ResponseBody
    @RequestMapping(value = "/test4/{id}/{name}")
    public String tet4(@PathVariable("id") String id,
                       @PathVariable("name") String name,
                       @RequestParam(value = "username",) String user,
                       @RequestParam("age") String age) {

        String data = id + name + user + age;
        System.out.println(data);
        return data;
    }   

2.4@RequestHeader

作用:获取请求头中某个key的值

用法跟@RequestParam相同

@ResponseBody
@RequestMapping("/getHeader")
public String getHeader(@RequestHeader(
    								//获取请求头中key为user-agent的值
    								 value = "user-agent",
                                       //没带时指定默认值
                                       defaultValue = "",
                               //设置是否必须携带  (默认是true 必须携带否则报错)
                                       required = false) String agent){
        return agent;
}

2.5@CookieValue(获取指定的Cookie)

作用:直接获取某个Cookie的值

原生Web开发获取Cookie

 //原生Web开发获取Cookie
Cookies[] cookies = request.getCookies(); //获取所有的Cookie
for(Cookie c : cookies){                  //遍历所有的Cookie 根据name属性获取想要的
    if("JESSIONID".equals(c.getName())){
    	String cookie = c.getValue(); 
    }
    .....
}

使用@CookieValue直接获取对应的Cookie

@ResponseBody
@RequestMapping("/getHeader")
public String getCookie(@CookieValue(
    								//获取key为JESSIONID的Cookie值
    								 value="JESSIONID"
                                     //不一定需要携带 (默认是true 必须携带否则报错)
                                     required = false,
                                     //没有携带是指定默认值
                                     defaultValue="")String jid){
                                                       
    return jid;
}

2.6@RequestBody(JSON封装为对象)

  • 作用:获取到POST请求的请求体内容 k1=v1&k2=v2…
  • 可以自动将请求体中的JSON格式的字符串转换为指定的Java对象(对象的集合也可以封装)
基本用法
    1、直接获取到请求体的内容,k1=v1&k2=v2&k3=v3....
    @ResponseBody
    @PostMapping("/testp")
    public String okc(@RequestBody String content ){
    System.out.println(content);
    return content;
}

 2、直接封装为对象 

@ResponseBody
@PostMapping("/set")
public List<User> ok(@RequestBody List<User> list){
        System.out.println(list);
        return list;
}

在这里插入图片描述

2.7参数直接封装为java对象

要求:

  • 前端的参数名要与JavaBean的属性名完全一致,否则无法封装,
  • Post请求和Get请求都可以封装为对象
  • 要求Java类的属性必须要有set方法,否则无法封装
  • 可以级联封装

代码示例

JavaBean

@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String username;
    private Integer id;
    private Double money;
    private Character sex;
    //有一个自定义的属性Address
    private Address address;
}
------------------------------
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Address {

    private String province;
    private String city;
    private Integer id;

}

前端

POST请求封装
<form action="/fz" method="post">
    <!--每一项的name属性都必须跟对应Java类的属性名相同,否则无法封装-->
    用户名<input type="text" name="username">
    id<input type="text" name="id">
    money<input type="text" name="money">
    sex<input type="text" name="sex">
    <!--级联对象属性的封装,name的值是对象.属性--><input type="text" name="address.province"><input type="text" name="address.city">
    id<input type="text" name="address.id">
    <button type="submit"></button>
</form>


GET请求自动封装:直接在请求的URL的?后进行拼即可,k1=v1&k2=v2。。。这种形式,要求key必须要和属性名相同
例如下面的URL,SpringMVC也可以自动封装
<!--http://localhost:8080/fz?username=张三&id=1&address.province=江苏...-->

Controller

@ResponseBody
@RequestMapping("/fz")
//直接将对象写在参数上 无需加任何注解 自动封装
public String fz(User user){
    System.out.println(user);
    return user.toString();
}

2.8使用Web原生API

可以在方法的参数中声明下面几种Web原生对象

HttpServletRequest            //常用
HttpServletResponse           //常用
HttpSession                   //常用
    //下面的几个不常用
java.security.Principal
Locale
InputStream
OutputStream
Reader
Writer

比如,想要在给Session中保存对象

@ResponseBody
@RequestMapping("/session")
//直接在参数列表中声明HttpSession即可
public String getWebYSAPI(HttpSession session){
    session.setAttribute("username", "zhangsan");
    return "Session信息已保存";
}

2.8@ResponseBody

  • 主要的作用:将返回的数据放到响应体中
  • 如果返回的是对象,自动将对象转换为JSON格式
  • 如果标注了@ResponseBody注解,则此方法不会进行跳转,默认都是向页面传值
    @ResponseBody
    @RequestMapping("/get")
    public List<User> oo(){
        ArrayList<User> users = new ArrayList<>();
        User user = new User("x", 1, 123d, 's', new Address("江苏省","南京市",1));
        User user1 = new User("x", 1, 123d, 's', new Address("江苏省","南京市",1));
        User user2 = new User("x", 1, 123d, 's', new Address("江苏省","南京市",1));
        users.add(user1);
        users.add(user);
        users.add(user2);
        return users;
    }

}

测试
在这里插入图片描述

2.9SpringMVC_HttpEntity获取请求头+请求体的内容

@RequestMapping("/get")
public String test(HttpEntity<String> entity){  //泛型规定的是请求体的泛型
    System.out.println(entity);
    return "user";
}

在这里插入图片描述

打印出了请求体+请求头的所有内容
在这里插入图片描述

HttpEntity的常用方法

@ResponseBody
@RequestMapping("/get")
public String oo(HttpEntity<String> entity){
   	//获取请求体中的数据 entity.getBody()
    /*
    前端发来的是JSON数据,获取的请求体打印的也是JSON格式的数据
    {"username":"x","id":1,"money":123.0,"sex":"s","address":{"province":"江苏省","city":"南京市","id":1}}
    */
    System.out.println(entity.getBody());
    
    
    
    //获取请求头中的数据 getHeaders 请求头本质上是一个Map,可以通过get方法获取指定的值
    /*
    [user-agent:"PostmanRuntime/7.26.10", accept:"*", postman-token:"8b65db3e-0fb5-4158-bff2-2904591c55a7", host:"localhost:8080", accept-encoding:"gzip, deflate, br", connection:"keep-alive", content-length:"108", Content-Type:"application/json;charset=UTF-8"]
    */
    System.out.println(entity.getHeaders());
    

    return "uu";
}

2.10设置响应的信息(头+体)

@ResponseBody
@RequestMapping("/test")
public ResponseEntity<String> get(){
    HttpHeaders headers = new HttpHeaders();
    //在请求中设置一个key为  set-Cookie 即让浏览器保存一个Cookie 原理就是这个
    //可以随意的设置
    headers.set("set-Cookie", "username=zhangsna");
    ResponseEntity responseEntity = new ResponseEntity<String>(headers,HttpStatus.OK);
    return responseEntity;
}

2.11请求头/体&响应头/体

请求头是请求对象携带的一些数据,比如Cookie,主机名,浏览器的信息等,
请求体都是存放的数据

响应头包含一些此次响应的一些数据,比如这次请求命令浏览器保存一个Cookie,那么响应头中就会加一个Set-Cookie的key,value就是k1=v1等

响应体中包含的就是此次请求返回的数据

3、将数据传递到页面

3.1Map Model ModelMap

  • 可以使用这三种方式,默认数据都是放到request域中的,跳转到页面时可以取出数据
  • 本质上都是一种,底层都是用BindingAwareModelMap这个类工作的
@RequestMapping("/map")
public String goOutt(Map<String,Object> map){
        map.put("map","map");
        return "out";
}

@RequestMapping("/model")
public String goOutt(Model model){
    model.addAttribute("model","model");
    return "out";
}

@RequestMapping("/modelmap")
public String goOutt(ModelMap modelMap){
    modelMap.addAttribute("modelMap","modelMap");
    return "out";
}

3.2ModelAndView(作为返回值)

也是在request域中存放的数据

ModelAndView可以作为方法的返回值

@RequestMapping("/modelandview")
public ModelAndView goOutt(){
    //可以在构造器中传入要跳转的页面,自动拼上前后缀
    ModelAndView modelAndView = new ModelAndView("out");
    //放数据
    modelAndView.addObject("modelAndView","modelAndView");
    return modelAndView;
}

3.3给Session中保存数据的方式

使用@ModelAndView注解 只能标在类上

后续不推荐使用,给Session中放数据还是使用原生API

//value属性是一个数组,表示只要你上面的任意一种对象中存了数据,只要key指定的名,就在session中也放一份
//type属性值不管你的key是什么名,只要value是指定的类型,也给session中放一份
@SessionAttributes(value = {"map"},types = {String.class})
@Controller
public class OutPutController {
    
}

4、REST风格

4.1概述

一句话表示就是请求的URL都相同,使用不同的请求方式(GET POST DELETE PUT)区分不同的操作

getBook?id=1         查询图书
deleteBook?id=1      删除图书
updateBook?id=1      更新图书
addBook?id           添加图书

  
使用REST风格后 REST风格的URL一般这样起名:/资源名/资源标识符
    简洁的URL提交请求,以请求方式区分对资源操作
    /book/1        GET请求方式     ----查询
    /book/1        PUT请求方式     ----修改
    /book/1        DELETE请求方式  ----删除
    /book          POST请求方式    ---添加

4.2存在的问题

在页面上只能发起两种方式的请求,GET和POST请求 使用过滤器解决问题 ,核心就是将POST请求改为PUT或者DELETE请求

后续开发很少使用

5、文件上传

5.1开发步骤

1、导入依赖

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.2.1</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>1.2</version>
</dependency>

2、配置Bean

@Configuration
public class FileUpLoadConfig {
	
    //配置文件上传MultipartResolver 注意:bean的id必须是multipartResolver
    //初始化时SpringMVC会从IOC容器中获取此Bean id就是multipartResolver
    @Bean
    public MultipartResolver multipartResolver() {
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        //设置上传文件最大值
        multipartResolver.setMaxUploadSize(1024 * 1024 * 20);
        //设置默认编码
        multipartResolver.setDefaultEncoding("utf-8");
        ...设置属性
        return multipartResolver;
    }
}

3、html

<!--文件上传方式使用post请求 enctype指定为multipart/form-data-->
<form action="/upload" method="post" enctype="multipart/form-data">
    上传文件<input type="file" name="headerimg">
<input type="submit">
</form>

4、Controller

@Controller
public class FileUploadController {


        @ResponseBody
        //配置文件上传解析器
        @RequestMapping("/upload")
        public String upload(Model model, 
                           	//文件信息封装在了MultipartFile里
                             @RequestParam("headerimg") 
                             MultipartFile file) throws IOException {                   //文件的源名字
 System.out.println("文件的名字getOriginalFilename"+file.getOriginalFilename());
//指定文件的保存路径
file.transferTo(new File("D:\\desk\\upfile\\"+file.getOriginalFilename()));
            
                model.addAttribute("msg", "文件上传成功");
                return "上传成功";
        }

}

5.2多文件上传

前两步跟单文件上传都一样

第三步

<form action="/upload" method="post" enctype="multipart/form-data">
    用户头像: <input type="file" name="headerimg">
    用户的图片: <input type="file" name="userimg">
    用户的合照: <input type="file" name="userandimg">
<input type="submit">

第四步

@Controller
public class FileUploadController {

        @ResponseBody
        //指定多个MultipartFile即可
        @RequestMapping("/upload")
        public String upload( @RequestParam("headerimg") MultipartFile file,
                        @RequestParam("userimg") MultipartFile userimg,                               @RequestParam("userAndimg") MultipartFile userAndimg){
		
            ...文件保存
        }
}

6、拦截器(HandlerInterceptor)

6.1概述及源码

  • SpringMVC提供了拦截器机制,允许目标方法之前进行一些拦截工作,或者在目标方法之后进行一些其他处理
  • SpringMVC的拦截器是一个接口HandlerInterceptor,实现拦截功能要实现此接口,并配置拦截那些方法以及拦截前后的操作等

接口的源码

public interface HandlerInterceptor {

	
//在目标方法之前调用,返回值是boolean,返回true表示执行目标方法(放行),返回false表示不放行
   default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
         throws Exception {
      return true;
   }

//在目标方法执行之后调用, 
   default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
         @Nullable ModelAndView modelAndView) throws Exception {
   }
	
    //在请求整个完成之后来到目标页面后调用 注意:只要preHandle放行了,即使是来到了错误页面此方法也会被执行(类似finally块)
   default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
         @Nullable Exception ex) throws Exception {
   }

}

6.2拦截器使用

1.实现HandlerInterceptor接口

public class MyInterceptor  implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      		//....一些处理
        return true; //true放行 false不放行(在此方法中做一些判断后续是否放行)
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //....一些处理
            }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {            
       //....一些处理
        }
}

2.注册拦截器并在SpringMVC中配置

@Configuration
public class ApplicationConfig implements WebMvcConfigurer {


    //注册拦截器并设置要拦截那些请求
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
            //注册自定义的拦截器     //配置要拦截哪些请求(默认拦截所有还可以进行排除策略)
  registry.addInterceptor(new MyInterceptor()).addPathPatterns("/inter");
    }
}

6.3运行流程

①preHanler--(如果放行)-->②目标方法--->③postHandler--->④来到页面--->⑤afterCompleion
| 注:只要放行之后,不管来到什么页面afterCompleion一定会执行
或
|
①preHanler--(不放行)--->后续的任何操作都不再执行

6.4多拦截器及其工作流程

1.多拦截器编码实现

继续自定义HandlerInterceptor的实现类然后在配置类中注册并配置即可

2.多拦截器的运行流程

正常流程

  • 拦截器的preHandler按照注册的顺序执行
  • 拦截器的postHandler按照注册的逆序执行,在页面渲染之前执行
  • 拦截器的afterCompletion按照注册的逆序执行
  • 已经放行了的拦截器,即使后面的拦截器不放行,但是已经放行的afterCompletion一定会执行

在这里插入图片描述

6.5拦截器源码

//在执行目标方法之前,会先执行拦截器的preHandler方法,只要有一个拦截器返回false(不放行),整个方法链直接return ,返回false的拦截器跳到afterCompetition执行
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
   return;
}

执行所有拦截器的preHandle的源码

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HandlerInterceptor[] interceptors = getInterceptors();
   if (!ObjectUtils.isEmpty(interceptors)) {
      for (int i = 0; i < interceptors.length; i++) {
         HandlerInterceptor interceptor = interceptors[i];
          //如果某一个拦截器不放行,直接进入AfterCompletion方法,执行前面放行的拦截器的
          //afterCompletion方法
         if (!interceptor.preHandle(request, response, this.handler)) {
            triggerAfterCompletion(request, response, null);
             //直接return false
            return false; 
         }
          //记录放行的拦截器的索引 便于后续执行放行的拦截器的afterCompletion方法
         this.interceptorIndex = i;
      }
   }
   return true;
}

执行所有拦截器的postHandle的源码

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
      throws Exception {
	//获取所有拦截器
   HandlerInterceptor[] interceptors = getInterceptors();
   if (!ObjectUtils.isEmpty(interceptors)) {
       //倒序遍历,逆序执行PostHandler(所有的拦截器都放行才会走到这一步)
      for (int i = interceptors.length - 1; i >= 0; i--) {
         HandlerInterceptor interceptor = interceptors[i];
         interceptor.postHandle(request, response, this.handler, mv);
      }
   }
}

afterCompletion总会执行

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
      throws Exception {
   HandlerInterceptor[] interceptors = getInterceptors();
   if (!ObjectUtils.isEmpty(interceptors)) {
       //倒序遍历拦截器,逆序执行preHandler放行的拦截器的afterCompletion方法
      for (int i = this.interceptorIndex; i >= 0; i--) {
         HandlerInterceptor interceptor = interceptors[i];
         try {
            interceptor.afterCompletion(request, response, this.handler, ex);
         }
         catch (Throwable ex2) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
         }
      }
   }
}

拦截器的流程总结

单拦截器(因为SpringMVC自带一个拦截器,这里只考虑自定义的拦截器)的执行流程,

执行preHandler方法,①如果返回false,流程直接结束
                  ②如果返回true,接着往下执行目标方法,然后执行postHandler方法,然后					   执行页面的渲染,最后执行afterCompletion方法

多个拦截器

根据拦截器的注册顺序正序执行preHandler方法,
 ①如果所有的拦截器都放行(返回true),那么就执行目标方法,然后倒序执行所有拦截器的postHandler方法,然后渲染页面,最后倒序执行所有拦截器的afterCompletion方法
 ②如果有一个拦截器不放行,那么就直接执行它前面的所有放行的拦截器的afterCompletion方法,然后返回false,方法链直接结束,它后面的所有拦截器的所有方法都不执行。

6.6拦截器中注入Bean为null

参考

7.异常处理

可以定制出现异常时的页面。。。

8.SpringMVC运行流程总结

1、所有请求被前端控制器DispatcherServlet拦截,调用doDispatch方法进行处理

2、在HandlerMapping中寻找该请求有处理器(Controller)处理,返回一个处理器执行链(包含拦截器)

3、根据当前的处理器获取对应的HandlerAdapter(适配器)

4、执行拦截器的preHandle方法

5、适配器执行目标方法,并返回ModelAndView

	 进行参数处理 。 。 。 

6、执行拦截器的postHandle方法

7、处理结果(页面渲染流程)
    
8、执行拦截器的afterCompletion方法

在这里插入图片描述

9、整合Spring

9.1为什么整合?

SpringMVC和Spring整合目的:分工明确

  • SpringMVC的配置文件就来配置和网站转发逻辑以及网站功能有关的,(视图解析器,拦截器,文件上传解析器)
  • Spring的配置文件用来配置和业务有关的(事务处理 数据源 xxx.... )

9.2整合

SpringMVC和Spring分容器

  • Spring的容器除了Controller不扫其他组件全扫
  • SpringMVC的容器只扫描控制器(Controller)

9.3父子容器

如果有两个容器,一个Spring的容器,一个SpringMVC的容器,Spring框架的默认将Spring的容器ApplicationContext作为父容器,SpringMVC的容器作为子容器子容器(MVC的容器)可以自动装配父容器(Spring的容器)中的Bean,但是父容器不能装配子容器中的Bean,例如子容器中的Controller可以自动装配父容器中的Service,但是父容器中的Service不能装配子容器中的Controller等

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shstart7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值