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