需要格外注意的一个地方:
**1.SpringMVC 严格的处理,拦截到的请求,如果没有映射的,都会报错 !!!
2.希望直接跳转到 WEB-INF/下的某个jsp 而不通过控制器,需要配置
打开 tomcat下 web.xml
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
调试技巧:
// 如何查看执行流程 : debug ;
在某个方法上面打断点 ,刷新浏览器,
发现 Java处理 异常 包括执行打印顺序 一致
public class Test {
public static void main(String[] args) {
test();
}
public static void test(){
int a = 9/0;
}
}
Exception in thread “main” java.lang.ArithmeticException: / by zero
at com.gs.test.Test.test(Test.java:28) //——————–>最上面的可能是引起异常的地方
at com.gs.test.Test.main(Test.java:17)
main 函数 调用 test()方法时候出现了异常,打印出来,test()自己没有处理,抛给调用者
main(), main里面也没有处理,然后又抛出
debug 追踪栈 :
// ————————–>最上面的(栈顶) 是 1.最后正 执行的 方法体,一层一层向下 是 2 “其调用者 ”——-> 3.”调用者的调用者 ”
单步跳过——-> 在当前调试指针跳到下一行,但是此行还未执行
我们在看源代码使用 第三方jar 如SpringMVC的时候,一旦debug启动会打印很多的栈
#
Spring url-pattern
/* 和 /的区别
测试发现
springDispatcherServlet
/*
/* 拦截 所有请求
/ 拦截所有请求 .css .jpg .. .action , 但是不拦截 .jsp
- 技巧之 常用的DTD,DispatcherServlet 中 xml的配置 加入到 快捷内容里面
a。@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入;
b.@Autowired默认是按照类型装配注入的,如果想按照名称来转配注入,则需要结合@Qualifier一起使用;
c.@Resource注解是又J2EE提供,而@Autowired是由Spring提供,故减少系统对spring的依赖建议使用
@Resource的方式;
d. @Resource和@Autowired都可以书写标注在字段或者该字段的setter方法之上
两个注解 却没有使用 setter 方法,Spring强大的处理 ,普通注入是需要写setter方法的
SpringMVC 的viewResolver 特别类似于 Struts2 的 /WEB-INF/jsp/list.jsp
视图解析器 :注解里面是 键值对 –>
@Resource
public class UserService
-->等价 @Resource("userService")
-->等价 @Resource(value="userService")
@ExceptionHandler({"ArithmeticException.class"})
-->等价 @ExceptionHandler(value={"ArithmeticException.class"})
======================>
SpringMVC 异常体系1
使用注解方式:(前提 扫描包)
/**
* 1. 在 @ExceptionHandler 方法的入参中可以加入 Exception 类型的参数, 该参数即对应发生的异常对象
* 2. @ExceptionHandler 方法的入参中不能传入 Map. 若希望把异常信息传导页面上, 需要使用 ModelAndView 作为返回值
* 3. @ExceptionHandler 方法标记的异常有优先级的问题.(如下面两个异常同时存在,抛出的是第二个,因为第二个匹配度高 )
*/
定义异常, 当发生此异常时候,自动 调用下面的方法 ;(只在本类 非全局 里面有效 ,很像Struts2 )
@ExceptionHandler({RuntimeException.class})
public ModelAndView handleArithmeticException2(Exception ex){
System.out.println("[出异常了]: " + ex);
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", ex);
return mv;
}
@ExceptionHandler({ArithmeticException.class})
public ModelAndView handleArithmeticException(Exception ex){
System.out.println("出异常了: " + ex);
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", ex);
return mv;
}
@RequestMapping("/testExceptionHandlerExceptionResolver")
public String testExceptionHandlerExceptionResolver(@RequestParam("i") int i){
System.out.println("result: " + (10 / i));
return "success";
}
定义全局异常 单独定义一个异常控制器类 注解是 @ControllerAdvice
-
- @ControllerAdvice: 如果在当前 Handler 中找不到 @ExceptionHandler 方法来出来当前方法出现的异常,
- 则将去 @ControllerAdvice 标记的类中查找 @ExceptionHandler 标记的方法来处理异常.
package com.atguigu.springmvc.test;
@ControllerAdvice
public class SpringMVCTestExceptionHandler {
@ExceptionHandler({ArithmeticException.class})
public ModelAndView handleArithmeticException(Exception ex){
System.out.println("----> 出异常了: " + ex);
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", ex);
return mv;
}
}
异常体系2
ResponseStatusExceptionResolver 类 –> 处理 @ResponseStatus注解
@注解其实也是一个类或接口
@ResponseStatus 注解
打开 此类 ResponseStatus
public @interface ResponseStatus{
HttpStatus value(); // 状态码 如404 ,403 ...
String reason(); // 返回结果
// JavaDoc上面 Mark a method or exception class 可以标记一个方法或一个异常类
}
注意:(方法上或 异常 类)@ResponseStatus注解,就 会被SpringMVC的ResponseStatusExceptionResolver处理(总控制器调用的视图解析器 ),
且,打来ResponseStatusExceptionResolver的源代码发现,有异常时候,ResponseStatusExceptionResolver返回页面(状态码 和 信息)
无异常时候,只返回给页面信息.
例子:
@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="用户名和密码不匹配!")
public class UserNameNotMatchPasswordException extends RuntimeException{
/**
*
*/
private static final long serialVersionUID = 1L;
}
@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="用户名和密码不匹配!")
public class UserNameNotMatchPasswordException extends RuntimeException{
/**
*
*/
private static final long serialVersionUID = 1L;
}
异常体系3
DefaultHandlerExceptionResolver (视图解析器)
对一些特殊的异常进行处理, 比如:
NoSuchRequestHandlingMethodException
HttpRequestMethodNotSupportedException
HttpMediaTypeNotSupportedException
HttpMeidaTypeNotAcceptableException 等
这个异常是系统默认的;出现错误 就会执行
异常体系4
SimpleMappingExceptionResolver
源代码分析:
打开SimpleMappingExceptionResolver ->
一步一步追踪,发现它处理异常和 #异常体系1# 中方式一样,返回ModelAndView,
把异常信息放入 域对象中, 其中一段
mv.addObject(this.exceptionAttribute,(Exception)ex);return mv;
this.exceptionAttribute 打开发现 值 ="exception";
故通过前台jsp ${exception} 可以打印出来
但是我们想改变 域对象中的 键值对 ${ex} 打印
仅需:
<!-- 配置使用 SimpleMappingExceptionResolver 来映射异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionAttribute" value="ex"></property>
//配置这里即可
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop> // xx.properties, 集中处理异常. key="异常全类名 "; error是视图名 error.jsp
</props>
</property>
</bean>
======================================== end =================================
## 在控制器的类定义及方法定义处都可标注
@RequestMapping
– 类定义处:提供初步的请求映射信息。相对于 WEB 应用的根目录
– 方法处:提供进一步的细分映射信息。相对于类定义处的 URL。若
类定义处未标注 @RequestMapping,则方法处标记的 URL 相对于
WEB 应用的根目录
• DispatcherServlet 截获请求后,就通过控制器上
@RequestMapping 提供的映射信息确定请求所对应的处理
方法。
用的不多
@RequestMapping 除了可以使用请求 URL 映射请求外,
还可以使用请求方法、请求参数及请求头映射请求
• @RequestMapping 的 value、method、params 及 heads
分别表示请求 URL、请求方法、请求参数及请求头的映射条
件,他们之间是与的关系,联合使用多个条件可让请求映射
更加精确化。
• params 和 headers支持简单的表达式:
– param1: 表示请求必须包含名为 param1 的请求参数
– !param1: 表示请求不能包含名为 param1 的请求参数
– param1 != value1: 表示请求包含名为 param1 的请求参数,但其值
不能为 value1
– {“param1=value1”, “param2”}: 请求必须包含名为 param1 和param2
的两个请求参数,且 param1 参数的值必须为 value1
REST 风格的增删改查
浏览器现在仅仅支持 GET POST 请求,Spring3.0提供了HiddenHttpMethodFilter
可以将这些请求转换为标准的http方法,使得支持GET Post PUT DELETE
//查看源代码:
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
/** Default method parameter: {@code _method} */
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = DEFAULT_METHOD_PARAM;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String paramValue = request.getParameter(this.methodParam);
if ("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
HttpServletRequest wrapper = new HttpMethodRequestWrapper(request, method);
filterChain.doFilter(wrapper, response);
}
else {
filterChain.doFilter(request, response);
}
}
配置此filter : web.xml
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
例子:
GET请求
<a href="springmvc/testRest/1"/>
PUT 请求 (更新)
<form action="springmvc/testRest/1" method="post"> //删除
<input type="hidden" name="_method" value="PUT" /> // 会被 HiddenHttpMethodFilter 转换为PUT 请求
<input type="submit" value="测试"/>
</form>
POST 新增
<form action="springmvc/testRest" method="post"> //删除
<input type="submit" value="测试"/>
</form>
DELETE 请求
<form action="springmvc/testRest/1" method="post"> //删除
<input type="hidden" name="_method" value="DELETE" /> // 会被 HiddenHttpMethodFilter 转换为DELETE 请求
<input type="submit" value="测试"/>
</form>
public class SpringMVCTest{
/**
Rest 风格的URL
以crud为例:
/order POST
/order/1 PUT update?id=1
/order/1 GET get?id=1
/order/1 DELETE delete?id=1
*/
@RequestMapping(value="/testRest/{id}",method=RequestMethod.PUT)
public String testRestPut(@PathVariable Integer id ){
System.out.println("testRest Put" + id );
}
@RequestMapping(value="/testRest/{id}",method=RequestMethod.DELETE)
public String testRestDELETE(@PathVariable Integer id ){
System.out.println("testRest DELETE" + id );
}
}
/**
* Rest 风格的 URL. 以 CRUD 为例: 新增: /order POST 修改: /order/1 PUT update?id=1 获取:
* /order/1 GET get?id=1 删除: /order/1 DELETE delete?id=1
*
* 如何发送 PUT 请求和 DELETE 请求呢 ? 1. 需要配置 HiddenHttpMethodFilter 2. 需要发送 POST 请求
* 3. 需要在发送 POST 请求时携带一个 name=”_method” 的隐藏域, 值为 DELETE 或 PUT
*
* 在 SpringMVC 的目标方法中如何得到 id 呢? 使用 @PathVariable 注解
*
*/
注意:
@RequestMapping(value="/testRest/{id}",method=RequestMethod.DELETE)
public String testRestDELETE(@PathVariable Integer id ){
System.out.println("testRest DELETE" + id );
}
// @PathVariable 绑定的不是请求参数哦 而是url 的占位符 -->赋值给Integer id 了
@RequestParam 可以绑定“请求参数”
<a href="testRequestParam?username=wangli"></a>
public String testRequestParam(@RequestParam(value="username") String uname ) {
//<a href="testRequestParam"></a> 会报错,因为前台没传username 参数
}
// @RequestParam(value="username") ---->等价 @RequestParam("username")
public String testRequestParam(@RequestParam(value="username",required=false) String uname ) {
// 改为此即可 ,那么可以不传 username 参数
}
public String testRequestParam(@RequestParam(value="age",required=false,defaultValue="0") int age ) {
//defaultValue="0" 注意,这个必须设,如果前台没有传入age 参数,那么int age是不能接受null 的,当然Integer age也可以,就不需要defaultValue了
@CookieValue
得到客户端的cookie (因为浏览器提交表单参数会默认带一个cookie:即JSESSIONID:xxx )
故 这里测试后台获取前台浏览器JSESSIONID的值 (也可以得到其他cookie哦 )
public String testCookie(@CookieValue("JSESSIONID") String sessionId){
System.out.println(sessionId);
return "success";
}
使用POJO 作为 参数
SpringMVC支持级联属性
Address 类; 省略
User user {
private String username;
private String password;
private Address address;
.. setter getter ..
}
表单
<input type="text" name="username"/>
<input type="text" name="password"/>
<input type="text" name="address.city"/>
<input type="text" name="address.provice"/>
Controller
public String handler(User user){
return "success";
}
SpringMVc支持 Servlet原生API
HttpServletRequest
HttpServletResponse
HttpSession
java.security.Principal
InputStream
OutputStream
Reader
Writer
Locale
public void testServletAPI(Writer out,HttpServletRequest request){
out.write("hello SpringMVC ");
}
处理模型数据
SpringMVC
1.ModelAndView ,通过此对象添加模型数据 域对象
2.Map 及 Model org.springframework.ui.Model,org.springframework.ui.ModelMap java.util.Map
3. @SessionAttributes 将模型中某个属性暂存到HttpSession中;
4.@ModelAttribute; 方法入参标注该注解后 入参的对象就会放到数据模型中
控制器处理方法的返回值如果为 ModelAndView, 则其既
包含视图信息,也包含模型数据信息。
• 添加模型数据:
– MoelAndView addObject(String attributeName, Object
attributeValue)
– ModelAndView addAllObject(Map<String, ?> modelMap)
• 设置视图:
– void setView(View view)
– void setViewName(String viewName)
public ModelAndView testModelAndVie(){
String viewname = "success";
ModelAndView modelAndView = new ModelAndView(viewname);
modelAndView.addObject("time",new Date());
return modelAndView;
}
#
Spring MVC 在内部使用了一个
org.springframework.ui.Model 接口存
储模型数据
• 具体步骤
– Spring MVC 在调用方法前会创建一个隐
含的模型对象作为模型数据的存储容器。
– 如果方法的入参为 Map 或 Model 类
型,Spring MVC 会将隐含模型的引用传
递给这些入参。在方法体内,开发者可以
通过这个入参对象访问到模型中的所有数
据,也可以向模型中添加新的属性数据
例子:
public String testMap(Map<String,Object> map ){
map.put("names",Arrays.asList("Tom","wangli","Mike"));
return "success";
}
@SessionAttributes (此注解只能放在类上面 )
注解 :
public @interface SessionAttributes{
String[] value() default{};
Class<?>[] types() default{};
}
例子:
@SessionAttributes({"user"}) //2. @SessionAttributes(value={"user"},types={String.class}) // 此时
@Controller
public class SpringMVCTest{
@RequestMapping("/testSesisionAttributes")
public String testSesisionAttributes(Map<String,Object> map){
User user = new User("tom","123");
map.put("user",user); // 此时不仅放在request里面,也会放在session里 jsp: ${requestScope.user.username} ${sessionScope.user.username}
map.put("school","schoolString ");// 上面@ 注解 即使用了属性名指定,也可以通过对象类型class
return "success";
}
}
# ModelAttribute的使用
update 操作是有两种情况的:
{
User.class id,username,birthday(Date)
1. <form>
<input type="hidden" name="id" value="1"/>
<input type="text" name="username"></input>
</form>
后台直接更新
User user = new User(); // 接收前台参数,但是第三个参数没有传入,birthday=null;
session.update(user); // 这种更新,本来不希望修改第三个字段,现在一下置 为null了
2. User user = userService.getById(model.getId());// 先从数据库取出数据
然后对此user 修改,更新
session.update(user); // ------->字段3 无影响
}
例子:
public SpringMVCTest{
// 如果在某个类里面都有此方法拦截,那么增删该查更加容易,只要前台传入id ,直接操作此对象,SpringMVC神来之笔 ----->
@ModelAttribute, 会在本类中每个方法前都调用,相当于拦截器
public void getUser(@RequestParam("id") Integer id,Map<String,Object> map){
if(id!= null){
// 模拟从数据库中取到对象
User user = userService.getById(id);
map.put("user",user);//
}
}
@RequestMapping("/testModelAtribute")
public String testModelAtribute(User user){
System.out.println("修改后:"+user);
return "success";
}
//上面的定义的一个方法上面@ModelAttribute就像一个拦截器一样,先被执行,里面已经存在参数里(数据库里面的对象,且放入域对象中.)
然后 我们访问/testModelAtribute url时候,SpringMVC 做的处理应该是
取出内存中的User对象(应该和属性名无关push(obj).),把表单数据setter进这个对象.
我们发现参数里面的(User user)没有 new 就使用了,说明Spring帮我们创建好的对象..
实验证实确实如此 ,User对象 和 引用名 没有关系 ,操作的都是同一个对象
具体分析:
//执行流程
/**
* 1. 有 @ModelAttribute 标记的方法, 会在每个目标方法执行之前被 SpringMVC 调用!
* 2. @ModelAttribute 注解也可以来修饰目标方法 POJO 类型的入参, 其 value 属性值有如下的作用:
* 1). SpringMVC 会使用 value 属性值在 implicitModel 中查找对应的对象, 若存在则会直接传入到目标方法的入参中.
* 2). SpringMVC 会以 value 为 key, POJO 类型的对象为 value, 存入到 request 中.
*/
* 注意: 在 @ModelAttribute 修饰的方法中, 放入到 Map 时的键需要和目标方法入参类型的第一个字母小写的字符串一致!
*
* SpringMVC 确定目标方法 POJO 类型入参的过程
* 1. 确定一个 key:
* 1). 若目标方法的 POJO 类型的参数木有使用 @ModelAttribute 作为修饰, 则 key 为 POJO 类名第一个字母的小写
* 2). 若使用了 @ModelAttribute 来修饰, 则 key 为 @ModelAttribute 注解的 value 属性值.
* 2. 在 implicitModel 中查找 key 对应的对象, 若存在, 则作为入参传入
* 1). 若在 @ModelAttribute 标记的方法中在 Map 中保存过, 且 key 和 1 确定的 key 一致, 则会获取到.
* 3. 若 implicitModel 中不存在 key 对应的对象, 则检查当前的 Handler 是否使用 @SessionAttributes 注解修饰,
* 若使用了该注解, 且 @SessionAttributes 注解的 value 属性值中包含了 key, 则会从 HttpSession 中来获取 key 所
* 对应的 value 值, 若存在则直接传入到目标方法的入参中. 若不存在则将抛出异常.
* 4. 若 Handler 没有标识 @SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key, 则
* 会通过反射来创建 POJO 类型的参数, 传入为目标方法的参数
* 5. SpringMVC 会把 key 和 POJO 类型的对象保存到 implicitModel 中, 进而会保存到 request 中.
*
* 源代码分析的流程
* 1. 调用 @ModelAttribute 注解修饰的方法. 实际上把 @ModelAttribute 方法中 Map 中的数据放在了 implicitModel 中.
* 2. 解析请求处理器的目标参数, 实际上该目标参数来自于 WebDataBinder 对象的 target 属性
* 1). 创建 WebDataBinder 对象:
* ①. 确定 objectName 属性: 若传入的 attrName 属性值为 "", 则 objectName 为类名第一个字母小写.
* *注意: attrName. 若目标方法的 POJO 属性使用了 @ModelAttribute 来修饰, 则 attrName 值即为 @ModelAttribute
* 的 value 属性值
*
* ②. 确定 target 属性:
* > 在 implicitModel 中查找 attrName 对应的属性值. 若存在, ok
* > *若不存在: 则验证当前 Handler 是否使用了 @SessionAttributes 进行修饰, 若使用了, 则尝试从 Session 中
* 获取 attrName 所对应的属性值. 若 session 中没有对应的属性值, 则抛出了异常.
* > 若 Handler 没有使用 @SessionAttributes 进行修饰, 或 @SessionAttributes 中没有使用 value 值指定的 key
* 和 attrName 相匹配, 则通过反射创建了 POJO 对象
*
* 2). SpringMVC 把表单的请求参数赋给了 WebDataBinder 的 target 对应的属性.
* 3). *SpringMVC 会把 WebDataBinder 的 attrName 和 target 给到 implicitModel.
* 近而传到 request 域对象中.
* 4). 把 WebDataBinder 的 target 作为参数传递给目标方法的入参.
*/
}
注意##########注意: 如果实在不太清楚源代码的流程 ,且同时使用
容易 出现异常:是因为在访问url @RequestMapping("/hello")时候,取出域对象找不到.)))) 没有指定放入域对象中 键-值 (1.没有指定 键名,且map.put("写的不是User类的小写",user)),默认根据"小写user"找的,没找到。2.根据指定的名称找,又没找到 3. 最后 直接去@SessionAttributes 中查找出现错误的,明确指定就不会了
@SessionAttributes("形参属性名") // 修饰目标方法形参 或类型
和@ModelAttribute 同时使用时,那么 map("user(User类的小写字母)",user); 即可
Demo1:
@Controller
@SessionAttributes("u")
public class UserAction {
@ModelAttribute
public void getUser(@RequestParam(value="id",required=false) Integer id , Map<String,Object> map){
System.out.println("-------->拦截请求<------------");
// 模拟从数据库中取出User对象
if(id!=null){
User user = new User(3,"王立","3352@qq.com");
map.put("user", user);
}
}
@RequestMapping("/hello")
public String hello(User u ){ // 或写为:-------> public String hello(@ModelAttribute("user") User u )
System.out.println("----------->hello");
System.out.println(u);
return "success";
}
}
Demo2 :
@Controller
@SessionAttributes("u")
public class UserAction {
@ModelAttribute
public void getUser(@RequestParam(value="id",required=false) Integer id , Map<String,Object> map){
System.out.println("-------->拦截请求<------------");
// 模拟从数据库中取出User对象
if(id!=null){
User user = new User(3,"王立","3352@qq.com");
map.put("abc", user);
}
}
@RequestMapping("/hello")
public String hello(@ModelAttribute("abc") User u ){
System.out.println("----------->hello");
System.out.println(u);
return "success";
}
}
Demo3 :
找不到 abc ,
@Controller
@SessionAttributes("u") //作用 ,把请求request域对象中 参数放入同时放入session中 .
public class UserAction {
@ModelAttribute
public void getUser(@RequestParam(value="id",required=false) Integer id , Map<String,Object> map){
System.out.println("-------->拦截请求<------------");
// 模拟从数据库中取出User对象
if(id!=null){
User user = new User(3,"王立","3352@qq.com");
map.put("abc", user);
}
}
@RequestMapping("/hello")
public String hello(User uuuu ){ (由于@SessionAttributes("u")原因 ) 抛出异常 --------> // 1.没有指定,按照User类小写"user" 没有找到域对象,2. 没有指定根据"键" 找 3.到 @SessionAttributes("u")根据 “u”找,也没有找到对应的对象,抛出异常
System.out.println(u);
return "success";
}
}
Demo4 :
@Controller
@SessionAttributes("u")
public class UserAction {
@ModelAttribute
public void getUser(@RequestParam(value="id",required=false) Integer id , Map<String,Object> map){
System.out.println("-------->拦截请求<------------");
// 模拟从数据库中取出User对象
if(id!=null){
User user = new User(3,"王立","3352@qq.com");
map.put("user", user);
map.put("love","I love you ");
}
}
@RequestMapping("/hello")
public String hello(User u ){ // --------->等价于 public String hello(@ModelAttribute("user") User u ); //且 @ModelAttribute的作用之一就是默认把"user"放入request域对象中,故前台可以直接${user.name},无需再次map.put("user",user);
System.out.println(u); // ---------------->发现2 ,前台不仅可以取出${user} , 且可以取出 @ModelAttribute 栈里面所有 属性 ${love },猜测: SpringMVC 把ModelAttribute栈里所有键值都放到了一个新 栈 里;
return "success";
}
}
重点:: Session ———>异常再 分析:
1. 知道了Model 数据Model 绑定的流程, 即使不写
public String hello(User u )
默认 --> public String hello (@ModelAttribute("user") User user ) // 控制器就是根据 这个字符串 "user" 去域对象中找的, 【横插一刀 @ModelAttribute】 request.getAttribute("user")没找到对象 ---->session.getAttribute("user")没有此对象,异常
如果没有配置
/*
@ModelAttribute
public void getTxxx(){
.........
map.put("user",user);
}
*/
但配置了 SessionAttributes("user") ,那么就会去找,没找到就异常了 .
- 一般没有@SessionAttributes()注解,或者@SessionAttributes(“名称”) 和 “user” 不一致。 就不会出现那种异常
最终 解决方案:
1.配置 @ModelAttribute方法 即可 (拦截 请求,令 域对象中 存在 “user”:” 值 “) 存在
- 去掉 @SessionAttributes()注解
- @SessionAttributes(“notuserString”)注解 ,名称和 public String hello (@ModelAttribute(“user”) User user ) 不一样即可 ………
其实 形参(String id ,String name,User user)—>都是有默认注解的.
就像Struts2 值栈 接收参数一样.;
SpringMVC形参没有写注解时候前台可以不传参数
写了注解后 ,没有指定defaultValue=”“,required=false; 则必须传入值 ;
#### redirect: 当成指示符,其后的字符串作为 "URL" 来处理
– redirect:success.jsp:会完成一个到 success.jsp 的重定向的操作
– forward:success.jsp:会完成一个到 success.jsp 的转发操
@RequestMapping(“testRedirect”)
public String testRedirect(){
return “redirect:/index.jsp”; // 重定向到 /index.jsp ,注意,这里的字符串被被当做 url 处理,不会加前后缀 了
}
@RequestMapping(“testForward”)
public String testRedirect(){
return “forward:/index.jsp”; // 请求 转发 到 /index.jsp ,注意,这里的字符串被被当做 url 处理,不会加前后缀 了
}
上面执行的视图解析器是什么呢:
UrlBasedViewResolver.class
if(viewName.startWith(REDIRECT_URL_PREFIX)){
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length);
..........
}
if(viewName.startWith(FORWARD_URL_PREFIX)){
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length);
..........
}
29 ###############################################
REST 风格 的增删该查
crud 操作
SpringMVC 的标签库 也可以回显
如表单标签 ,同struts一样 如 :
// 这个必须指定,否则默认值是 “command”
// 相当于 jstl 没有此属性,不会报错,顶多不显示
// 这些标签会回显 ,但是注意了,和struts2一样,这些属性 –>存在才行
// 最好使用jstl 标签
Action 中 !!
ActionContext.getContext().put(“employee”,employee);
Controller
map.put(“employee”,employee);
#### 数据格式化
// 得到类型转换出错的消息
@RequestMapping(“/emp”)
public String save(Employee employee,BindingResult result){
if(result.getErrorCount() > 0 ){
System.out.println(“出错了 “);
for(FieldError error:result.getFieldErrors() ){
System.out.println(error.getField()+":"+error.getDefaultMessage() );
}
return "redirect:/emps";
}
}
重点 :##
• 对属性对象的输入/输出进行 “格式化”,从其本质上讲依然
属于 “类型转换” 的范畴。
• Spring 在格式化模块中定义了一个实现
ConversionService 接口的
FormattingConversionService 实现类,该实现类扩展
了 GenericConversionService,因此它既具有类型转换的
功能,又具有格式化的功能
• FormattingConversionService 拥有一个
FormattingConversionServiceFactroyBean 工厂类,
后者用于在 Spring 上下文中构造前者
• FormattingConversionServiceFactroyBean 内部已经注册了 :
#– NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性
使用 @NumberFormat 注解
#– JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型
的属性使用 @DateTimeFormat 注解
// 说明: SpringMVC默认的就是使用这个转换器 (只要<mvc:annotation-driven/> 就默认创建这个,无需我们配置此bean )
• 装配了 FormattingConversionServiceFactroyBean 后,就可
以在 Spring MVC 入参绑定及模型数据输出时使用注解驱动
了。 默认创建的
ConversionService 实例即为
FormattingConversionServiceFactroyBean
(接口)
使用注解后 :DispatcherServlet 会 调用 FormattingConversionService
默认创建的就是 ConversionService 实例 即为 FormattingConversionServiceBean
interface ConversionService =(DefaultFormattingConversionService) 1.类型转换 2.数据格式化
private Set<Conveter> converters =
@DateTimeFormatAnnotationFormatterFactory
@NumberFormatAnnotationFormatterFactory
@.....
@.....
@.....
....
…….
### 开发过程中: 如果要使用 自定义 + 注解 类型 格式化可以直接使用
标签默认创建了FormattingConversionServiceFactoryBean
<list>
<bean class="xxxx"/> // 自定义的 类
</list>
#
1.数据类型转换
2.数据类型格式化
3. 数据校验 {
(1).如何校验 ?注解
(2).验证出错转向那个页面
(3).错误消息? 如何显示,如何把错误消息进行国际化
}
JSR 303
• JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,(是个规范 )
它已经包含在 JavaEE 6.0 中 .
• JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max
等标准的注解指定校验规则,并通过标准的验证接口对 Bean
进行验证
注解 :
@Null
@NotNull
@AssertTrue 被注释的元素必须为true
@AssertFalse 被注释的元素必须为false
@Min(value)
@Max(value)
@DecimalMin
@Past 目标必须是一个过去的日期
@Future 必须是将来日期
……..
Hibernate Validator 是 JSR 303 的一个参考实现,除了支持所有标准的校验注解外
还支持 一下扩展 注解
@Email
@Length
@NotEmpty
@Range 被注解的元素必须在合适的范围内
• Spring 4.0 拥有自己独立的数据校验框架,同时支持 JSR
303 标准的校验框架。
• Spring 在进行数据绑定时,可同时调用校验框架完成数据校
验工作。在 Spring MVC 中,可直接通过注解驱动的方式
进行数据校验
• Spring 的 LocalValidatorFactroyBean 既实现了 Spring 的
Validator 接口,也实现了 JSR 303 的 Validator 接口。只要
在 Spring 容器中定义了一个
LocalValidatorFactoryBean,即可将其注入到需要数据校
验的 Bean 中。
• Spring 本身并没有提供 JSR303 的实现,所以必须将
JSR303 的实现者的 jar 包放到类路径下。
• 会默认装配好一个
LocalValidatorFactoryBean,通过在处理方法的入参上标
注 @valid 注解即可让 Spring MVC 在完成数据绑定后执行
数据校验的工作
• 在已经标注了 JSR303 注解的表单/命令对象前标注一个
@Valid,Spring MVC 框架在将请求参数绑定到该入参对象
后,就会调用校验框架根据注解声明的校验规则实施校验
• Spring MVC 是通过对 “处理方法” 签名的规约来保存校验结果
的:前一个 表单/命令对象 的校验结果保存到随后的 入参
中,这个保存校验结果的入参必须是 BindingResult 或
Errors 类型,这两个类都位于
org.springframework.validation 包中
##########
hibernate-validator-5.0.0.CR2
1. hibernate-validator-5.0.0.CR2.jar
2. hibernate-validator-annotation-proce..jar
required:lib
classmate-0.8.0.jar
jboss-logging-3.1.1.GA.jar
validation-api.1.1.0.CR1.jar
(el-api-2.2.jar ,javax.el-2.2.4.jar ,javax.el-api-2.2.4.jar)不需要
注意: 删除tomcat下面的el-api-2.2.jar),把上面()中jar copy到 tomcat下面
使用步骤 :
1. 加入 hibernate-validator-5 jar
2. SpringMVC中 添加 注解
3. JavaBean 上面加入对应 注解
4. 在目标方法 bean 类型前面 添加@ Valid注解
注意:
• 需校验的 Bean 对象和其绑定结果对象或错误对象时成对出现的,它们
之间不允许声明其他的入参
• Errors 接口提供了获取错误信息的方法,如 getErrorCount() 或 getFieldErrors(String field)
• BindingResult 扩展了 Errors 接口 Interface BindingResult extends Errors
public String handle 91(@Valid User user, BindingResult userBindingResult,String sessionId,ModelMap mm,@Valid Dept dept, Errors deptErrors){
User和其绑定结果的对象
Dept和其校验的结果对象
错误的回显: 简单:
回显全部错误
显示单个错误
#
定制 错误消息:
消息的 国际化
• 每个属性在数据绑定和数据校验发生错误时,都会生成一
个对应的 FieldError 对象。
• 当一个属性校验失败后,校验框架会为该属性生成 4 个消
息代码,这些代码以校验注解类名为前缀,结合
modleAttribute、属性名及属性类型名生成多个对应的消
息代码:例如 User 类中的 password 属性标准了一个 @Pattern 注
解,当该属性值不满足 @Pattern 所定义的规则时, 就会产生以下 4
个错误代码:
– Pattern.user.password
– Pattern.password
– Pattern.java.lang.String
– Pattern
• 当使用 Spring MVC 标签显示错误消息时, Spring MVC 会查看
WEB 上下文是否装配了对应的国际化消息,如果没有,则显示默认
的错误消息,否则使用国际化消息。
SpringMVC jar 包下面一定存在 默认的 i18n.properties 文件, 默认加载的是这个
定义 我们自己的进行覆盖
步骤:
1.
i18n.properties文件
NotEmpty.employee.lastName=lastName不能为空
Email.employee.email=Email 格式不合法
Past.employee.birth = 不能是一个过去的日期
• 若数据类型转换或数据格式转换时发生错误,或该有的参
数不存在,或调用处理方法时发生错误,都会在隐含模型
中创建错误消息。其错误代码前缀说明如下:
– required:必要的参数不存在。如 @RequiredParam(“param1”)
标注了一个入参,但是该参数不存在
– typeMismatch:在数据绑定时,发生数据类型不匹配的问题
– methodInvocation:Spring MVC 在调用处理方法时发生了错误
i18n.properties文件
required.employee.email = email 不能为null
typeMismatch.employee.birth=Birth 不是一个日期
methodInvocation.employee.email = 方法调用出现异常了 哈哈
springmvc.xml 配置国际化资源
#######
SpringMVC显示Json
(function(){().click(function(){
var url = this.href;
var args = {};
$.post(url,args,function(data){
for(var i =0;i
#######
44 . 国际化
关于国际化:
1. 在页面上能够 根据浏览器语言设置的情况对文本(而不是内容),时间,数值进行本地化处理
2.可以在bean 中获取国际化 资源文件 Locale对应的消息
3.可以通过超链接 切换Locale,而不再依赖于浏览器的语言设置 情况
解决:
1.使用 jstl 的fmt标签
2. 在bean 中注入ResourceBundleMessageSource 的示例.使用其对应的getMessage 方法即可
@Autowired
private ResourceBundleMessageSource resourceBundleMessageSource;
@RequestMapping
public String testI18n(Locale locale){
String val = resourceBundleMessageSource.getMessage("i18n.user",null,locale);
System.out.println(val);
return "i18n";
}
配置LocalResolver 和 LocaleChangeInterceptor
Struts2 : 方案: 配置请求参数的拦截器,拦截请求参数所对应的Locale,然后Locale放入到session里面 ,下次用时候直接从session中 使用这个Locale即可
SpringMVC的运行原理:
—–>获取 name=locale 的请求参数——->把第一步的locale请求参数解析为Locale 对象—–>获取 LocaleResolver 对象
(由LocaleChangeInterceptor完成 )—–>把Locale对象设置为Session属性—–> 从Session中获取Locale对象
(由 SessionLocaleResolver完成 )使用:
配置
<!-- 配置国际化资源文件 -->
## 拦截器
拦截器 是在 DispacherServlet doDispacher()调用
拦截器:
if(!mappendHandler.applyPreHandle(processedRequest,response )){
return ; // 如果返回 false , 直接return ,那么下面的目标方法就 不能执行了
}
进入此方法 ------>
// boolean applyPreHandle(processedRequest,response ){
if(getInterceptors()!=null ){ // ---- >从这里看出拦截器是个数组 ;
for(int i=0;i< getInterceptors().length;i++){
HandlerInterceptor interceptor = getInterceptors()[i];
if(!interceptor.preHandle(request,response,this.handler)){ // 调用 拦截器的 preHandle()方法
triggerAfterCompletion(request,response,null);
return false; // 一旦 前面第一个拦截器 return false ; 那么后面 的拦截器 都不被调用了
}
this.interceptorIndex = i;
}
}
return true;
}
void applyPostHandle(request,response,ModelAndView mv){
if(getInterceptors == null){
return;
}
for(int i =getInterceptors.length-1;i >=0; i--){
HandlerInterceptor interceptor = getInterceptors()[i];
interceptor.postHandle(request,response,this.handler,mv);
}
}
大概调用流程:
拦截器: 权限,日志,事务
if(!mappendHandler.applyPreHandle(processedRequest,response )){
return ; // 如果返回 false , 直接return ,那么下面的目标方法就 不能执行了
}
目标方法:
mv = ha.handle(processedRequest,response,mappendHandler.getHandler() );
applyDefaultViewName(request,mv);
拦截器: postHandle(),调用目标方法之后,但在渲染视图之前,可以对请求域中的属性或视图做出修改
mappendHandler.applyPostHandle(processedRequest,response,mv);
渲染视图:
processDispacheResult(processedRequest,response,mappendHandler,mv,dispatchException);
拦截器: afterCompletion(HttpServletRequest request,HttpServletResponse response,...); // 渲染视图之后调用 ,常用来 释放资源
综上:
FirstInterceptor implements HandlerInterceptors{
public boolean preHandle(HttpServletRequest request,.. response,Object handler){
}
/*
1.该方法在目标方法前被调用
2.若返回值是true,则继续调用后续 拦截器和目标方法
3.若返回值为 false,则不会再调用 后续拦截器和方法
*/
}
<!-- 方式一:配置自定义的拦截器 -->
<bean class="com.atguigu.springmvc.interceptors.FirstInterceptor"></bean>
<!-- 方式二:配置拦截器作用(或 不作用) 的路径 -->
<mvc:interceptor> <!--里面可以配置具体的属性 -->
<mvc:mapping path="/emps"/>
<bean class="com.atguigu.springmvc.interceptors.SecondInterceptor"></bean>
</mvc:interceptor>
#####
SpringMVC 和Spring的整合
1.