SpringMvc 第二天

spring05

回顾

spring的事务
	编程式事务:事务管理器代码与业务层代码耦合在一起,进行控制,开发中一般不用
		这里面涉及到3个接口,大家需要记住
		PlatformTransactionManager 事务管理器平台(导入spring-orm.jar)
			JDBC、mybatis使用DataSourceTransactionManager
			hibernate使用HibernateTransactionManager
			JPA使用JPATransactionManager
		TransactionDefinition 事务定义信息			
			隔离级别
            传播行为
             	REQUIRED:必须有一个事务
				SUPPORTS:支持当前事务,有就用,没有就不用
			超时时间
			是否只读
		 TransactionStatus 事务的运行状态
	声明式事务:
		前提:配置事务管理器
		通过配置的方式让事务控制和业务代码结合在一起,底层思想就是AOP(需要导入aspectweaver)
		xml
			使用xml做事务管理,一劳永逸
			使用tx:advice声明规则
			使用aop:config配置切面
		注解配置
			事务注解@Transactional(规则)
			注解事务开启<tx:annotation-driver/>或者纯注解使用@EnableTransactionManagement
springMVC是web框架,封装servlet共有的行为(前端控制器),使开发者关注业务本身
springMVC快速入门
	编写controller和jsp
	springmvc配置文件中配置:
		组件扫描
		mvc注解支持
		视图解析器(添加了前缀和后缀)
	web.xml中配置前端控制器:DispacherServlet  路径为:/ 配置springmvc的路径
	面试题:执行流程
springmvc常用注解
	@Controller
	@RequestMapping
		可以作用在类上和方法上
		value	访问路径
		method  声明处理哪种方式请求
springmvc接收请求
	接收请求参数
		简单类型:参数名和方法形参名称一样
		对象类型:参数名和对象属性名一样,在方法上声明javabean对象
		数组类型:页面上name值一样,在方法上声明数组对象,也可以封装成逗号隔开的字符串
		对象的集合属性:使用ognl表达式
	中文乱码:
		配置CharacterEncodingFilter,指定编码
	类型转换:对于个别类型不满足需要的时候就需要转换,例如:日期 支持"/" 不支持"-"
		方式1:@DateTimeFormat(pattern = "yyyy-MM-dd")
		方式2:编写自定义转换器实现Converter接口,注册给springmvc

内容介绍

  1. 文件上传
  2. 请求相关的注解
  3. 获取servlet原生的API(request,response,session)
  4. 放行静态资源
  5. 处理响应
  6. ajax请求
  7. restful编程风格
    • 之前: http://localhost/springmvc/user/delete?id=x
    • 现在: http://localhost/springmvc/user/x
  8. 异常统一处理

一 文件上传

1 前提

springmvc文件上传,底层使用commons-fileupload.我们使用的时候就需要把fileupload的依赖导入到工程中

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>

2 文件上传前端三要素

  1. 提供文件选择框:
  2. 表单提交方式必须为:method=“post”
  3. 表单的enctype属性必须为:multipart/form-data

3 springMVC框架完成文件上传

  1. 在springmvc.xml中配置文件解析器:CommonsMultipartResolver
    • 注意文件解析器的id必须为:multipartResolver
  2. 在Controller中,在方法参数上中提供一个 MultipartFile 类型的参数 , 参数名字为 input标签name属性
  3. 在方法中调用MultipartFile 的api进行获取上传文件的内容(例如:文件名字,流)
<form method="post" action="${pageContext.request.contextPath}/upload/test1" enctype="multipart/form-data">
    <fieldset>
        <legend>a_文件上传</legend>
        用户名:<input name="username"><br>
        图片:<input type="file" name="photo"><br>
        <input type="submit" value="提交">
    </fieldset>
</form>
<!--
        文件解析器
            id必须为multipartResolver
    -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--设置单次文件上传的总大小 单位:byte 例如:100k-->
    <property name="maxUploadSize" value="102400"/>
    <!--设置单个文件的大小 例如:50k-->
    <property name="maxUploadSizePerFile" value="51200"/>
</bean>
@Controller
@RequestMapping("upload")
public class A_UploadController {

    @RequestMapping("test1")
    public String test1(MultipartFile photo,String username) throws IOException {
        //获取用户名
        System.out.println(username);

        if (photo!=null) {
            //获取文件名称
            String filename = photo.getOriginalFilename();
            //设置随机文件名称
            filename = UUID.randomUUID().toString()+"_"+filename;

            //设置保存目录
            File dirPath = new File("e:/upload");
            if (!dirPath.exists()) {
                dirPath.mkdirs();
            }

            //保存文件
            photo.transferTo(new File(dirPath,filename));
        }

        return "success";
    }
}

线下阅读:https://blog.csdn.net/whc__/article/details/106168053

多文件上传:

<form method="post" action="${pageContext.request.contextPath}/upload/test2" enctype="multipart/form-data">
    <fieldset>
        <legend>a_文件上传</legend>
        用户名:<input name="username"><br>
        图片1:<input type="file" name="photo"><br>
        图片2:<input type="file" name="photo"><br>
        <input type="submit" value="提交">
    </fieldset>
</form>
@RequestMapping("test2")
public String test1(MultipartFile photo[],String username) throws IOException {
    //获取用户名
    System.out.println(username);

    if (photo!=null && photo.length>0) {
        for (MultipartFile ph : photo) {
            //获取文件名称
            String filename = ph.getOriginalFilename();
            //设置随机文件名称
            filename = UUID.randomUUID().toString()+"_"+filename;

            //设置保存目录
            File dirPath = new File("e:/upload");
            if (!dirPath.exists()) {
                dirPath.mkdirs();
            }

            //保存文件
            ph.transferTo(new File(dirPath,filename));
        }
    }

    return "success";
}

二 请求相关注解

@RequestParam

  • 作用:
    1. 可以给参数设置默认值;
    2. 可以重新映射(当页面上传递过来的参数和方法中参数名字不一致时)
    3. 可以将同名的参数封装list

@RequstHeader(了解)

  • 作用:获取指定的请求头的值,赋值给方法的参数

@CookieValue(了解)

  • 作用:获取指定cookie的值,赋值给方法的参数
<a href="${pageContext.request.contextPath}/requestAnno/test1?pageNum=4">b_请求相关的注解_RequestParam</a><br/>
<a href="${pageContext.request.contextPath}/requestAnno/test1?pageNum=4&pageSize=5">b_请求相关的注解_RequestParam</a><br/>
<a href="${pageContext.request.contextPath}/requestAnno/test2">b_请求相关的注解_RequestHeader_CookieValue</a><br/>
@Controller
@RequestMapping("requestAnno")
public class B_ReqeustAnnoController {

    @RequestMapping("test1")
    /*
        使用defaultValue属性设置默认值的时候
        使用value属性将页面上传递过来的参数绑定到方法中的参数上
     */
    public String test1(@RequestParam("pageNum") int pageNumber, @RequestParam(defaultValue = "10") int pageSize){
        System.out.println(pageNumber);
        System.out.println(pageSize);
        return "success";
    }

    /*
        获取浏览器信息
        获取session的id(名字为JSESSIONID的cookie中保存)
     */
    @RequestMapping("test2")
    public String test2(@RequestHeader("user-agent") String agent, @CookieValue("JSESSIONID") String sessionId){
        System.out.println(agent);
        System.out.println(sessionId);
        return "success";
    }
}

三 获得原生ServletAPI(重要)

获取request、response或session

方式1:在目标方法中添加参数即可

方式2:通过注入的方式

@Controller
@RequestMapping("api")
public class C_APIController {

    //方式1:在方法中声明参数
    @RequestMapping("test1")
    public String test1(HttpServletRequest request, HttpServletResponse response, HttpSession session){
        request.setAttribute("requestMsg","通过方法参数获取request");
        session.setAttribute("sessionMsg","通过方法参数获取session");
        System.out.println(request);
        System.out.println(response);
        System.out.println(session);
        return "success";
    }

    //方法2:通过@Autowired注入
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private HttpServletResponse response;//有点小bug,我们可以使用它作为占位符,不要轻易使用这种方式获取的response的api
    @Autowired
    private HttpSession session;
    @RequestMapping("test2")
    public String test2(){
        request.setAttribute("requestMsg","通过注入方式获取request");
        session.setAttribute("sessionMsg","通过注入方式获取session");
        System.out.println(request);
        System.out.println(response);
        System.out.println(session);
        return "success";
    }
}

四 静态资源放行(重要)

当请求来的时候,servlet容器就会去web.xml中寻找对应路径处理的servlet,若找不到,就需要交给servlet容器中提供的两个servlet(JspServlet和DefaultServlet)来处理请求

  • JspServlet:用来处理jsp页面
  • DefaultServlet:用来处理别的serlvet都处理不了的请求,用来兜底儿的.例如:静态资源

现在我们使用springmvc框架,将前端控制器的路径设置为了"/",这时候就覆盖了默认servlet,访问静态资源的时候就报404了.

方式1:开启静态资源servlet支持

修改springmvc.xml

<!--开启tomcat默认静态资源映射 若访问的是静态资源就交给web服务器中默认servlet处理-->
<mvc:default-servlet-handler/>

方式2:通过springMVC框架自定义映射关系

修改springmvc.xml

<!--建立静态资源映射关系
	可以使用通配符
        mapping:请求路径
        location:项目中资源的路径
-->
<mvc:resources mapping="/img/**" location="/img/"/>
<mvc:resources mapping="/css/**" location="/css/"/>

方式3:修改dispatcherservlet的匹配规则

例如:我们将DispatcherServlet的路径设置为:*.do,这样的话springmvc就只处理以.do结尾的请求,默认的servlet就又好使了.我们只需要在请求路径后面加".do",其他的不用动

五 SpringMVC的响应

1 响应方式介绍

无非是转发或者重定向或者直接写回数据

springmvc通过方法返回值来说明响应回去的方式

  • 返回String
    • 默认就是转发
    • 使用关键字说明转发还是重定向
  • 返回void
    • 使用原生的api进行操作
    • 一般做文件下载用
  • 返回ModelAndView(一般springmvc的底层使用)

2 String返回

默认方式

就是转发

若要想往request域中存放数据的话可以通过一下几种方式来操作

  • 方式1:使用request的api进行操作
  • 方式2:在方法上声明参数Map、ModelMap或者Model,通过他们往域中存放数据
    • ModelMap或者Model都是map
@Controller
@RequestMapping("string")
public class D_StringController {
    //默认方式:转发
    //往request域中存放数据
    @RequestMapping("test1")
    public String test1(HttpServletRequest request, Map map, ModelMap modelMap, Model model){
        request.setAttribute("requestKey","requestValue");
        map.put("mapKey","mapValue");
        modelMap.put("modelMapKey","modelMapValue");
        model.addAttribute("modelKey","modelValue");
        return "msg";
    }
}

转发和重定向

通过关键字完成转发或者重定向

  • forward:/转发的内部路径
  • redirect:站外路径
  • redirect:/站内的内部路径
    • 不需要写项目应用路径
    <a href="${pageContext.request.contextPath}/string/test1">D_string返回值_默认转发</a><br/>
    <a href="${pageContext.request.contextPath}/string/test2">D_string返回值_forward转发</a><br/>
    <a href="${pageContext.request.contextPath}/string/test3">D_string返回值_redirect重定向外部资源</a><br/>
    <a href="${pageContext.request.contextPath}/string/test4">D_string返回值_redirect重定向内部资源</a><br/>

//forward进行转发
@RequestMapping("test2")
public String test2(Map map){
    map.put("mapKey","forward转发过去");
    return "forward:/WEB-INF/pages/msg.jsp";
}

//redirect重定向站外资源
@RequestMapping("test3")
public String test3(){
    return "redirect:https://www.baidu.com/";
}

//redirect重定向站内资源
@RequestMapping("test4")
public String test4(){
    return "redirect:/html/1.html";
}


3 void返回

需要使用原生的api进行转发,重定向或者打印数据

一般文件下载的时候使用

    <a href="${pageContext.request.contextPath}/void/test1">e_void返回值_转发</a><br/>
    <a href="${pageContext.request.contextPath}/void/test2">e_void返回值_重定向</a><br/>

//响应的返回值为void
@Controller
@RequestMapping("void")
public class E_VoidController {

    //转发
    @RequestMapping("test1")
    public void test1(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setAttribute("requestKey","void方式");
        request.getRequestDispatcher("/WEB-INF/pages/msg.jsp").forward(request,response);
    }

    //重定向
    @RequestMapping("test2")
    public void test2(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.sendRedirect(request.getContextPath()+"/html/1.html");
    }
}

4 ModelAndView返回

方式一:在方法中创建

方式二:在方法的参数上声明

//返回值为ModelAndView
@Controller
@RequestMapping("mv")
public class F_MVController {
    @RequestMapping("test1")
    //方法中创建modelandview对象
    public ModelAndView test1(){
        ModelAndView mv = new ModelAndView();

        //往域中放入数据
        mv.addObject("modelKey","使用modelAndView返回");
        //设置跳转的路径(和返回字符串的规则一样)
        mv.setViewName("msg");

        return mv;
    }

    @RequestMapping("test2")
    //方法中创建modelandview对象
    public ModelAndView test2(){
        ModelAndView mv = new ModelAndView();

        //往域中放入数据
        mv.addObject("modelKey","使用modelAndView返回");
        //设置跳转的路径(转发)
        mv.setViewName("forward:/WEB-INF/pages/msg.jsp");

        return mv;
    }

    @RequestMapping("test3")
    //方法参数中声明modelandview对象
    public ModelAndView test3(ModelAndView mv){
        //设置跳转的路径(重定向)
        mv.setViewName("redirect:/html/1.html");
        return mv;
    }
}

5 @SessionAttributes

作用在类上,从request域中将数据拷贝一份,往session域中存放数据.

@Controller
@RequestMapping("sessionAttr")
@SessionAttributes(types = {String.class},value = {"age"})//往session中存放哪些类型的数据或者哪些名字的数据
public class G_SessionAttrController {
    @RequestMapping("test1")
    public String test1(Map map){
        map.put("username","tom");
        map.put("age",18);
        map.put("sex","男");
        return "sessionAttr";
    }
}

要想清除session域中的数据,需要通过SessionStatus对象的方法setComplete()清除.

大家还是使用原生的session进行操作吧~~

六 ajax

  • 自行导入axios.js

axios.get(“路径?参数”).then(resp=>{})

axios.get(“路径”,{

​ params:{

​ key:value,

​ key:value

​ }

}).then(resp=>{})

axios.post(“路径?参数”,{

​ key:value,

​ key:value

}).then(resp=>{});


@Controller
@RequestMapping("ajax")
public class H_AjaxController {

    @RequestMapping("test1")
    public void test1(User user, HttpServletResponse response) throws IOException {
        System.out.println(user);

        response.setContentType("application/json;charset=utf-8");
        response.getWriter().print("{\"success\":true,\"msg\":\"用户名可以使用\"}");
    }
}

若要使用Springmvc中两个ajax相关的注解,需要把jackson依赖导入,否则就会报415的错误

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.9</version>
</dependency>

@RequestBody

将json类型的请求参数封装成指定的对象

@ResponseBody

将方法的返回值转成json字符串,写回浏览器.注意方法若返回的字符串,会原样输出到浏览器上

<script src="js/axios-0.18.0.js"></script>
<script>
    document.querySelector("#btn0").onclick=function () {
        /*
        axios.get("ajax/test1?username=tom&age=18").then(resp=>{
            console.log(resp.data);
            console.log(resp.data.msg);
        });*/

        axios.get("${pageContext.request.contextPath}/ajax/test1",{
            params:{
                "username":"tom",
                "age":18
            }
        }).then(resp=>{
            console.log(resp.data);
            console.log(resp.data.msg);
        });
    }


    document.querySelector("#btn1").onclick=function () {
       axios.post("${pageContext.request.contextPath}/ajax/test2",{
            "username":"tom",
            "age":18
        }).then(resp=>{
            console.log(resp.data);
            console.log(resp.data.msg);
        });
    }
</script>

@RequestMapping("ajax")
@Controller
public class H_AjaxController {

    @RequestMapping("test1")
    public void test01(User user, HttpServletResponse response) throws IOException {
        System.out.println(user);
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().print("{\"success\":true,\"msg\":\"请求成功\"}");
    }

    /*
        @RequestBody  可以将请求中的json数据直接封装成对象
        @ResponseBody 可以将返回值转成json字符串响应回去
     */
    @RequestMapping("test2")
    @ResponseBody
    public Map test02(@RequestBody User user, HttpServletResponse response) throws IOException {

        System.out.println(user);

        HashMap<String, Object> map = new HashMap<>();
        map.put("success",true);
        map.put("msg","请求成功");

        /*
            //之前的方式
            String jsonStr = new ObjectMapper().writeValueAsString(map);
            response.setContentType("text/html;charset=utf-8");
            response.getWriter().print(jsonStr);
         */
        return map;
    }

七 RESTful

restful是一种软件架构的设计风格。主要用于客户端和服务器交互的软件架构。如果我们软件设计使用了此风格,咱们的架构层次更加分明,简洁、易于缓存…
通过请求路径+请求方式来简化路径编写

  • 查询操作 发送get请求 /user/12
  • 删除操作 发送delete请求 /user/12
  • 保存操作 发送post请求 /user
  • 更新操作 发送put请求 /user

浏览器提交的方式只能为二种:get、post

使用ajax或者请求调试工具发送不同的请求方式即可.

spring提供的注解:

  • @PathVariable(“uid”) :获取路径上的变量(/user/{uid}),赋值给方法的参数.
  • @GetMapping(“路径”):处理get请求的路径
  • @PostMapping(“路径”):处理post请求的路径
  • @DeleteMapping(“路径”)…
  • @PutMapping(“路径”)…
  • @RestController:一个顶俩 @Controller+@ResponseBody
<input type="button" value="查询id=1用户" id="btn_get">
<input type="button" value="删除id=1用户" id="btn_delete">
<input type="button" value="保存用户" id="btn_post">
<input type="button" value="更新用户" id="btn_put">

<script>
    document.querySelector("#btn_get").onclick=function () {
        axios.get("${pageContext.request.contextPath}/user/1").then(resp=>{
            console.log(resp.data);
        });
    }

    document.querySelector("#btn_delete").onclick=function () {
        axios.delete("${pageContext.request.contextPath}/user/1").then(resp=>{
            console.log(resp.data);
        });
    }

    document.querySelector("#btn_post").onclick=function () {
        axios.post("${pageContext.request.contextPath}/user",{
            "username":"tomcat",
            "age":20
        }).then(resp=>{
            console.log(resp.data);
        });
    }

    document.querySelector("#btn_put").onclick=function () {
        axios.put("${pageContext.request.contextPath}/user",{
            "username":"tomcat",
            "age":20
        }).then(resp=>{
            console.log(resp.data);
        });
    }
</script>

//@Controller
//@ResponseBody

@RestController //一个顶俩
public class I_RestUserController {

    //@RequestMapping(value = "/user/{uid}",method = RequestMethod.GET)
    @GetMapping("/user/{uid}")//等价于上面
    //@ResponseBody
    public Map findById(@PathVariable("uid") int id){
        System.out.println("调用service查询数据库,获取指定的用户:"+id);
        Map<String, Object> map = new HashMap<>();
        map.put("success",true);
        map.put("msg","返回id为"+id+"的用户");
        return map;
    }

    //@RequestMapping(value = "/user/{uid}",method = RequestMethod.DELETE)
    @DeleteMapping("/user/{uid}")
    //@ResponseBody
    public Map deleteById(@PathVariable("uid") int id){
        System.out.println("调用service删除指定的用户:"+id);
        Map<String, Object> map = new HashMap<>();
        map.put("success",true);
        map.put("msg","删除id为"+id+"的用户");
        return map;
    }

    @PostMapping("/user")
    //@ResponseBody
    public Map save(@RequestBody User user){
        System.out.println("调用service保存用户:"+user);
        Map<String, Object> map = new HashMap<>();
        map.put("success",true);
        map.put("msg","已经保存用户");
        return map;
    }

    @PutMapping("/user")
    //@ResponseBody
    public Map update(@RequestBody User user){
        System.out.println("调用service更新用户:"+user);
        Map<String, Object> map = new HashMap<>();
        map.put("success",true);
        map.put("msg","已更新用户");
        return map;
    }
}


八 异常统一处理

当用户访问的资源不存在的时候,默认会返回一个404页面.当服务器后台出错的时候,会给用户返回一个500页面.

用户不知道404和500代表的时候,并且这个页面太丑了.应该给用户一个友好的页面

  • 可以使用web服务器中提供的统一错误友好页面
  • 也可以使用springmvc提供的异常统一处理方式

1 web中的统一错误友好页面

只需要在web.xml中通过error-page标签来配置错误友好页面即可

<error-page>
    <!--当出现404状态码的时候,服务器就会将请求转发到404的页面上去-->
    <error-code>404</error-code>
    <location>/WEB-INF/pages/error/404.jsp</location>
</error-page>
<error-page>
    <error-code>500</error-code>
    <location>/WEB-INF/pages/error/500.jsp</location>
</error-page>

2 springmvc中的统一处理方式

以后无论那层出现异常,都要抛出去,最后有前端控制器找到处理异常解析器去解析异常.

只能处理内部异常

xml方式

步骤分析:

  1. 编写一个类,实现一个接口 HandlerExceptionResolver
  2. 重写里面的方法
  3. 在springmvc.xml中进行配置
public class MyExceptionhandler implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelAndView mv = new ModelAndView();
        //把错误信息放入model中
        mv.addObject("msg",ex.getMessage());

        //转发到error/mvc_500.jsp
        mv.setViewName("error/mvc_500");

        return mv;
    }
}

<!--配置全局错误处理解析器-->
<bean class="cn.itcast.web.handler.MyExceptionhandler"/>

注解方式

步骤分析:

  1. 编写一个类,放在组件扫描能扫描到的包下,添加注解@ControllerAdvice
  2. 编写方法,返回值为ModelAndView,
  3. 在方法上添加注解@ExceptionHandler,通过value属性设置只处理那些异常;
@ControllerAdvice //控制器的增强类
public class MyAnnoExceptionHandler {

    @ExceptionHandler(Exception.class) //处理全部异常
    public ModelAndView handlerException(){
        ModelAndView mv = new ModelAndView();

        //转发到error/mvc_500.jsp
        mv.setViewName("error/mvc_500");

        return mv;
    }
}

上一篇:Servlet[springmvc]的Servlet.init()引发异常


下一篇:SpringMVC