SpringMVC学习笔记

需要格外注意的一个地方:

**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  
  1. 技巧之 常用的DTD,DispatcherServlet 中 xml的配置 加入到 快捷内容里面
  2. a。@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入;

    b.@Autowired默认是按照类型装配注入的,如果想按照名称来转配注入,则需要结合@Qualifier一起使用;

    c.@Resource注解是又J2EE提供,而@Autowired是由Spring提供,故减少系统对spring的依赖建议使用

       @Resource的方式;
    

    d. @Resource和@Autowired都可以书写标注在字段或者该字段的setter方法之上

两个注解 却没有使用 setter 方法,Spring强大的处理 ,普通注入是需要写setter方法的

  1. 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

    1. @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();  // 状态码  如404403 ...
    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") ,那么就会去找,没找到就异常了 .
  1. 一般没有@SessionAttributes()注解,或者@SessionAttributes(“名称”) 和 “user” 不一致。 就不会出现那种异常

最终 解决方案:

1.配置 @ModelAttribute方法 即可 (拦截 请求,令 域对象中 存在 “user”:” 值 “) 存在

  1. 去掉 @SessionAttributes()注解
  2. @SessionAttributes(“notuserString”)注解 ,名称和 public String hello (@ModelAttribute(“user”) User user ) 不一样即可 ………

其实 形参(String id ,String name,User user)—>都是有默认注解的.

就像Struts2 值栈 接收参数一样.;

SpringMVC形参没有写注解时候前台可以不传参数
写了注解后 ,没有指定defaultValue=”“,required=false; 则必须传入值 ;

################ 25 #### 国际化 只要把jstl(jstl.jar,standard.jar)放入到 lib 类路径下 下面,那么Spring自动把 View 转换为 JstlView, 国际化 放在src下面 i18n.properties 内容: i18n.username=Username i18n.password=Password i18n_zh_CN.properties 内容: i18n.username=用户名 i18n.password=密码 i18n_en_US.properties i18n.username=Username i18n.password=Password jstl国际化使用的是 标签 标签 Spring里面配置国际化; springmvc.xml 中 26.##### SpringMVC 确实强大 在 Web-Inf 下面的 资源jsp文件。我们希望通过index.jsp —>success.jsp 希望直接连过去,而不想通过Controller springmvc.xml 中配置 直接转发的页面 实际开发中 通常都需要配置 mvc:annotation-driven标签 (当然没有配置 的情况下可以不配置) 问题是,一旦配置
#### 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);

############# 问题1: • 优雅的 REST 风格的资源URL 不希望带 .html 或 .do 等后缀 • 若将 DispatcherServlet 请求映射配置为 /,则 Spring MVC 将捕获 WEB 容器的所有请求,包括静态资源的请求, SpringMVC 会将他 们当成一个普通请求处理,因找不到对应处理器将导致错误。 • 可以在 SpringMVC 的配置文件中配置 的方式解决静态资源的问题: – 将在 SpringMVC 上下文中定义一个 DefaultServletHttpRequestHandler,它会对进入 DispatcherServlet 的 请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 WEB 应用服务器默认的 Servlet 处理,如果不是静态资源的请求,才由 DispatcherServlet 继续处理 – 一般 WEB 应用服务器默认的 Servlet 的名称都是 default。若所使用的 WEB 服务器的默认 Servlet 名称不是 default,则需要通过 defaultservlet-name 属性显式指定 问题2: 使用REST风格 即 POST,GET,PUT,DELETE 四种 请求,其他两种请求需要通过把 POST 请求进行转换 (隐藏表单域) web.xml 进行转换 HiddenHttpMethodFilter org.springframework.web.filter.HiddenHttpMethodFilter HiddenHttpMethodFilter /* 但是 普通 删除 SpringMVC Delete 仅仅支持GET, 如果删除都写一个表单,太蛋疼了 !! 方案: 只能通过jQuery 改变 了 (1).在本页面加一个表单 Delete (2). (function(){(".delete").click(function(){ var href = (this).attr("href"); ("form").attr("action", href).submit(); return false; }); }) 问题再次出现: dispacherServlet 拦截器配置的是 / ,此设置拦截所有请求 ,连资源文件都不放过 . 实际上也是一个请求 http://localhost:8080/sprinvmc/js/jquery.js • 优雅的 REST 风格的资源URL 不希望带 .html 或 .do 等后缀 • 若将 DispatcherServlet 请求映射配置为 /,则 Spring MVC 将捕获 WEB 容器的所有请求,包括静态资源的请求, SpringMVC 会将他 们当成一个普通请求处理,因找不到对应处理器将导致错误。 • 可以在 SpringMVC 的配置文件中配置 的方式解决静态资源的问题: – 将在 SpringMVC 上下文中定义一个 DefaultServletHttpRequestHandler,它会对进入 DispatcherServlet 的 请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 WEB 应用服务器默认的 Servlet 处理,如果不是静态资源的请求,才由 DispatcherServlet 继续处理 – 一般 WEB 应用服务器默认的 Servlet 的名称都是 default。若所使用的 WEB 服务器的默认 Servlet 名称不是 default,则需要通过 defaultservlet-name 属性显式指定 SpringMVC 严格的处理,拦截到的请求,如果没有映射的,都会报错 !!! ####### 修改操作 : 需要回显, editUI , SpringMVC使用@ModelAttribute注解 例子: SpringMVC 修改和添加共用一个页面 : //———->如果 域对象中 employee.id 为空,默认执行添加 操作 LastName: //———->如果 域对象中 employee.id 不为空,执行修改操作 Email: Action : //添加 add.action @RequestMapping(value=”/emp”, method=RequestMethod.POST) public String save(@Valid Employee employee, Errors result, Map
#### 数据格式化

// 得到类型转换出错的消息

@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 = 方法调用出现异常了 哈哈 
  1. springmvc.xml 配置国际化资源


  2. #######

    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";
    }
  1. 配置LocalResolver 和 LocaleChangeInterceptor

    Struts2 : 方案: 配置请求参数的拦截器,拦截请求参数所对应的Locale,然后Locale放入到session里面 ,下次用时候直接从session中 使用这个Locale即可

    SpringMVC的运行原理:

    —–>获取 name=locale 的请求参数——->把第一步的locale请求参数解析为Locale 对象—–>获取 LocaleResolver 对象
    (由LocaleChangeInterceptor完成 )

    —–>把Locale对象设置为Session属性—–> 从Session中获取Locale对象
    (由 SessionLocaleResolver完成 )

    使用:

    1. 配置

      <!-- 配置国际化资源文件 -->
      

## 拦截器
拦截器 是在 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.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值