16.@ModelAttribute注解详解

@ModelAttribute注解可以标注在方法上,也可以标注在方法的参数上

    1.标注在方法上,该方法回提前运行:如下,当浏览器访问到该类下的所有请求时,都会先执行@ModelAttribute标注的方法,并且每次执行请求时都会访问
                @Controller
                public class MyFirstController {
                    @RequestMapping("/hello")
                    public String updateBook()
                        ....
                    )
                    @ModelAttribute(value = "abc")
                    public void selectBookByBookname(){
                        
                    }
                }
            
            1.2细研究@ModelAttribute标注在方法上的作用(提前运行)
                    @ModelAttribute(value = "abc")
                    public void selectBookByBookname(
                                Map <String,Object> map, 
                                Book book, 
                                @RequestParam(value = "bookName") String bookName, 
                                HttpServletRequest request,
                                HttpServletResponse response){
                        System.out.println("ModelAttribute:模拟查询书名:"+book.getBookName());
                        System.out.println("map:"+map.getClass());
                        //1.模拟从数据库中根据书名查询出该条图书记录
                        Book book_select=new Book("西游记",12.0 ,"吴承恩");
                        System.out.println("ModelAttribute查询出的图书记录:"+book_select);
                        //2.并将查询出的book放入到传入的Map/Model/ModelMap
                        map.put("hhh",book_select);
                    }
                    最重要的是请求参数的确定,因为springmvc的请求参数比较随意,可以是map,可以是实体类,可以是带注解的一般属性,也可以是原生的api
                    方法开始执行:
                        1.因在web.xml中配置了前端控制器:DispatcherServlet-->所有的请求最终都会调用到该类的:doDispatch方法
                                <servlet>
                                    <servlet-name>dispatcherServlet</servlet-name>
                                    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
                                    <load-on-startup>1</load-on-startup>
                                </servlet>
                                <servlet-mapping>
                                    <servlet-name>dispatcherServlet</servlet-name>
                                    <!--
                                      /*和/都是拦截所有请求
                                      /:会拦截所有请求,但是不会拦截jsp页面,能保证jsp页面正常访问
                                      /*:范围更大,还会拦截jsp页面,jsp页面拦截后就不会再显示了
                                    -->
                                    <url-pattern>/</url-pattern>
                                </servlet-mapping>
                        2.DispatcherServlet类中的方法执行细究:
                            protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
                                ...
                                1.通过请求路径获取方法的处理器:就是哪个类来处理这个请求:
                                mappedHandler = getHandler(processedRequest);
                                2.获取方法的适配器:获取到的是AnnotationMethodHandlerAdapter注解方法执行器--->包括了三种:
                                1.HttpRequestHandlerAdapter  2.SimpleControllerHandlerAdapter   3.AnnotationMethodHandlerAdapter
                                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                                ...
                                3.执行方法的适配器:
                                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                                该方法内容如下:发现其返回的是一个ModelAndView对象,即方法执行不管返回什么,springmvc都会将其封装成一个ModealAndView
                                    public ModelAndView handle(...){
                                        ...
                                        4.调用处理程序方法
                                        return invokeHandlerMethod(request, response, handler);
                                        该方法内容如下:
                                            protected ModelAndView invokeHandlerMethod(){
                                                ...
                                                5.创建一个BindingAwareModelMap并传入,我们知道放入BindingAwareModelMap中的数据相当于放进requesst域中
                                                ExtendedModelMap implicitModel = new BindingAwareModelMap();
                                                6.执行目标方法
                                                Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
                                                具体代码如下:
                                                    public final Object invokeHandlerMethod(...){
                                                        7.判断执行请求目标方法的类中是否包含@SessionAttributes注解
                                                        for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
                                                            从sessionAttributeStore中拿到值,并放入BindingAwareModelMap,前台可以执行从request域中获取,也可以从session域中获取
                                                        }
                                                        8.判断请求目标方法类中是否包含@ModelAttribute注解
                                                        for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {
                                                            9.获取执行ModelAttribute注解方法的参数:这里比较重要
                                                            Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
                                                            具体代码如下:返回的是一个请求参数object数组
                                                                private Object[] resolveHandlerArguments(...){
                                                                    10.创建一个和方法参数长度一样的Object数组args,并依次解析往里面放值
                                                                    for (int i = 0; i < args.length; i++) {
                                                                        ...
                                                                        11.判断参数上有没有注解,如果有注解,循环遍历并绑定值
                                                                        for (Annotation paramAnn : paramAnns) {
                                                                            RequestParam...
                                                                            RequestHeader...
                                                                            RequestBody...
                                                                            CookieValue...
                                                                            PathVariable...
                                                                            ModelAttribute..
                                                                        }
                                                                    }
                                                                    一个参数的注解数不能大于1个,要不会抛异常
                                                                    if (annotationsFound > 1) {
                                                                        抛异常
                                                                    }
                                                                    12.没有注解分析
                                                                   if (annotationsFound == 0) {
                                                                        13.解析参数是否是原生api:ServletRequest,ServletResponse,HttpSession,Principal,Principal,InputStream,Reader,OutputStream,Writer
                                                                        如果是,返回对应的原生对象,如果不是,返回一个新创建的object对象
                                                                        Object argValue = resolveCommonArgument(methodParam, webRequest);
                                                                        ...
                                                                        14.判断参数是否是Model或者Map,如果是将之前创建的BindingAwareModelMap赋值给他
                                                                        if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {
                                                                            args[i] = implicitModel;
                                                                        }
                                                                   }
                                                                   13.参数分析
                                                                       1.如果是Model或着Map,将之前创建的BindingAwareModelMap赋值给他:args[i] = implicitModel;
                                                                       2.如果是原生的api:ServletRequest,ServletResponse等,将其赋值给args数组
                                                                       3.如果是普通类型int,string等:如果带了注解如:
                                                                           @RequestParam(value = "bookName") String bookName,会通过requset.getParameterValues("bookName"),去获取页面传来的值,并绑定
                                                                           如果没带注解:如String bookName,会根据变量名去获取并进行绑定
                                                                       4.如果是引用类型,实体类:Book
                                                                           4.1会创建一个Book对象,并将页面传入的参数进行绑定
                                                                           4.2并将绑定后的book对象放入到BindingAwareModelMap中
                                                                }
                                                            14.获取完带@ModelAttribute(value = "abc")方法的请求参数,开始执行带@ModelAttribute(value = "abc")的方法
                                                            15.获取@ModelAttribute(value = "abc")的value值
                                                            String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value();
                                                            16.如果@ModelAttribute没有value值,则直接将方法的返回值作为attrname
                                                                如如果方法为:则attrname为:void,如果返回值为Book,则attrname为book
                                                                    @ModelAttribute
                                                                    public void selectBookByBookname(..){..}
                                                            17.执行带@ModelAttribute的方法,并将返回值按照attrname放入到BindingAwareModelMap中
                                                                Object attrValue = attributeMethodToInvoke.invoke(handler, args);
                                                                implicitModel.addAttribute(attrName, attrValue);
                                                            结论:
                                                                1.执行完@ModelAttribute的方法方法后,会将执行结果(如果不为空),和请求参数中的实体类对象放入到BindingAwareModelMap中
                                                                2.由源码知,参数中的Modeal或者Map都会由BindingAwareModelMap代替,即往Modeal中或者Map中放值,最终会放入到BindingAwareModelMap中
                                                                @ModelAttribute(value = "abc")
                                                                public void selectBookByBookname(Map <String,Object> map...){
                                                                    Book book_select=new Book("西游记",12.0 ,"吴承恩");
                                                                    放入map中即放入到BindingAwareModelMap中
                                                                    map.put("hhh",book_select);
                                                                }
                                                                这里写成以下:也会将返回值Book按照abc为key存入到BindingAwareModelMap中,但是这个只能往里存一个book对象,不像map,可以存放很多的数据或对象,推荐使用上面
                                                                @ModelAttribute(value = "abc")
                                                                public Book selectBookByBookname(Map <String,Object> map...){
                                                                    Book book_select=new Book("西游记",12.0 ,"吴承恩");
                                                                    return book_select;
                                                                }
                                                            18.带@ModelAttribute的方法执行完毕,开始执行真正的目标方法:使用同样的方法获取请求参数
                                                            Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
                                                                18.1需要注意的是实体类值:真正的目标方法如下:
                                                                        public String updateBook(
                                                                         @RequestParam(value = "bookName") String bookName,Map<String, Object> map,
                                                                         HttpServletRequest request,@ModelAttribute("hhh") Book book){
                                                                             ..
                                                                         }
                                                                     Book对象值得确认,@ModelAttribute("hhh") Book book
                                                                         1.会先去BindingAwareModelMap根据hhh去获取book
                                                                         2.如果标注了@SessionAttributes注解,回去session中根据对应的key去找,找不到会抛出异常,所以一般不用@SessionAttributes注解去操作session域
                                                                         3.上述都找不到,会创建一个新的book对象
                                                            19.执行真正的目标方法
                                                            return handlerMethodToInvoke.invoke(handler, args);
                                                        }
                                                    }
                                            }
                                    }
                            }

 

posted @ 2022-05-08 22:13  努力的达子  阅读(312)  评论(0编辑  收藏  举报