spring mvc

spring mvc需要spring-webmvc-xxx.jar。

spring mvc中的几个组件:

    DispatcherServlet:前端控制器,是org.springframework.web.servlet.DispatcherServlet

    HandlerMapping:处理器映射器,是HandlerMapping接口,仅有一个 HandlerExecutionChain getHandler(HttpServletRequest request)方法,用来获得处理器链,常用的实现类是RequestMappingHandlerMapping(org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping)

    Handler:处理器,处理用户请求的,需要我们自己开发

    HandlerAdapter:处理器适配器,是HandlerAdapter接口,常用的实现类是RequestMappingHandlerAdapter(org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter)

    ViewResolver:视图解析器,是ViewResolver接口,常用的实现类是InternalResourceViewResolver(org.springframework.web.servlet.view.InternalResourceViewResolver)

    View:视图页面,需要我们自己开发

用了spring mvc后请求-响应的基本流程是:

1.用户发起request请求,请求至DispatcherServlet前端控制器

2.DispatcherServlet前端控制器请求HandlerMapping处理器映射器查找Handler处理器

3.HanlderMapping处理器映射器找到Handler处理器后将该Handler处理器返回给DispatcherServlet前端控制器

4.DispatcherServlet前端控制器调用处理器适配器去执行Handler处理器

5.Handler执行完成返回ModelAndView(org.springframework.web.servlet.ModelAndView类,封装了Object view和ModelMap model属性)

6.HandlerAdapter处理器适配器将ModelAndView返回给DispatcherServlet前端控制器

7.DispatcherServlet前端控制器调用ViewResolver视图解析器进行视图解析,解析后生成view

8.ViewResolver视图解析器将view返回给DispatcherServlet前端控制器

9.DispatcherServlet调用view的渲染视图的方法,将模型数据填充到request域中

10.DispatcherServlet向用户响应结果

首先,需要在web.xml文件中配置DispatcherServlet前端控制器,通过contextConfigLocation属性(DispatcherServlet父类FrameworkServlet的一个属性 private String contextConfigLocation;)来指定spring mvc配置文件的路径,也可以配置此Servlet的<load-on-startup />,从而让tomcat一启动就创建DispatcherServlet实例(非必须,可选),配置如下:

<servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

我们在spring mvc配置文件需要配置三大组件:HandlerMapping处理器映射器、HandlerAdapter处理器适配器、ViewResolver视图解析器,配置示例如下:

<context:component-scan base-package="com.kou.controller"></context:component-scan>
    
<bean  class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>

<bean  class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean>
        
<bean  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>

在上面的配置文件中,配置了RequestMappingHandlerMapping处理器映射器、RequestMappingHandlerAdapter处理器适配器、InternalResourceViewResolver视图解析器,还用<context:component-scan />元素指定了自动扫描的包。

用RequestMappingHandlerMapping处理器映射器、RequestMappingHandlerAdapter处理器适配器开发Handler处理器时,不要求处理器类实现任何接口。

常用注解的用法:

@controller

标注某个类,表示该类是Handler处理器类。

@RequestMapping

既可以标注Handler处理器类中的方法,也可以标注Handler处理器类。标注Handler处理器类的话,该类中的所有方法都会用到在@RequestMapping中配置的属性。@RequestMapping有很多属性很常用:

method = { RequestMethod.POST }表示该方法只能匹配POST请求,如果不配的话,表示可以匹配任意方式的请求;

value = { "/test1" }表示该方法匹配请求路径是/test1的请求,value属性必配。此时如果同时用@RequestMapping来标注Handler处理器类的话,假如配置value属性为value = "/delete",则刚刚配置的方法就匹配路为/delete/test1的请求。

value属性支持Ant风格的url配置,?匹配任意一个字符,*匹配任意个字符,**匹配任意层路径,如/delete/**/test1可以匹配/delete/aaa/test1、/delete/aaa/bbb/test1。

params = { "userName=kou", "age" }表示该方法匹配的请求的请求参数必须包含userName和age,且userName参数的值必须为"kou"。

如果请求url在处理器中没有方法可以匹配,假如url是/getAll,此时服务器会报警告:No mapping found for HTTP request with URI [/springmvc/getAll] in DispatcherServlet with name 'dispatcherServlet',浏览器会展示404页面。

@PathVariable

与@RequestMapping配合使用,修饰处理器方法的形参,将请求url中占位符的值绑定到形参中,相当于给形参设值,用例:

@Controller
@RequestMapping(value = "/hello")
public class HelloController {
    @Resource
    private HelloService helloService;

    @RequestMapping(value = { "/dd/{Id}" })
    public void getAll(HttpServletRequest request, HttpServletResponse response,
            @PathVariable(value = "Id") String id) {
        System.out.println(id);
    }
}

如果请求的url是/hello/dd/10,则此时将匹配到getAll()方法,@PathVariable从url中截取出ID对应的字符串,并赋值给参数id,此时可以在方法体拿到id的值,为10。

这里要注意两点:

@PathVariable的value属性的值要和RequestMapping的value属性的花括号的值保持一致,否则页面会报500,提示Missing URI template variable

最好让@PathVariable修饰的参数的类型为String。假如请求的url是/hello/dd/100a,则此时也能匹配到getAll()方法,如果@PathVariable修饰的参数的类型为Integer或是其他非String类型的话,可能报转换异常MethodArgumentTypeMismatchException: Failed to convert value of type [java.lang.String] to required type [java.lang.Integer]; nested exception is java.lang.NumberFormatException: For input string: "100a"

@RequestParam

修饰处理器方法的形参,会先得到前端请求参数的值然后再赋给指定的形参

@Controller
@RequestMapping(value = "/hello")
public class HelloController {
    @Resource
    private HelloService helloService;

    @RequestMapping(value = { "/dd" })
    public void getAll(HttpServletRequest request, HttpServletResponse response,
            @RequestParam(value = "username") String userName) {
        System.out.println(userName);
    }
}

如果请求url是/hello/dd,则会匹配到getAll()方法,@RequestParam会先执行request.getParameter("username")得到username参数的值,然后赋值给形参userName,总的来说就相当于执行userName=request.getParameter("username"),此时我们就可以在方法体中获得userName的值。如果username对应的参数值为null,则userName也会为null。如果设置了@RequestParam的话,则此请求必须有username参数,否则页面会报400,提示Required String parameter 'username' is not present,后台程序不报错。如果我们不想前台页面报错的话,只需设置@RequestParam的required属性值为false,默认值是true。

值得注意的是,@RequestParam不仅可以从get请求、一般post请求中获取请求参数的值,也可以从multipart/form-data的post请求中得到请求参数的值,见下文介绍的文件上传。

@RequestHeader

获取请求报头中的指定的属性值并绑定到指定形参中

@CookieValue

获取请求中指定的Cookie值并绑定到指定形参中

一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理,如返回字符串"result",则配置的InternalResourceViewResolver视图解析器会解析此字符串,返回/WEB-INF/jsp/result.jsp。但是,如果返回的字符串以forward:或者redirect:开始,则spring mvc会对这种字符串特殊处理,不会再交给试图解析器去解析,而是将forward:或者redirect:当成指示符、其后的字符串作为url来处理,也就是说会直接转发或者重定向到后半部分字符串指定的url。比如说返回字符串是"forward:/index.jsp",则会直接转发到index.jsp页面。

如果要符合RESTful风格,我们需将DispatcherServlet的请求映射由*.action变为/,但这时spring mvc将捕获所有请求,包括静态资源的请求,spring mvc会将他们看成一个普通请求处理,因找不到对应处理器将导致错误。比如说,在WEB-INF目录下,新建一个image目录,专门存放图片,这个时候访问/image/111.png,服务器会报警告: No mapping found for HTTP request with URI [/springmvc/image/111.png] in DispatcherServlet with name 'dispatcherServlet'。有3种解决办法:

1.在web.xml中配置默认Servlet(如Tomcat的org.apache.catalina.servlets.DefaultServlet)的映射,用来匹配所有可能的、各种类型的静态资源,示例:

<!-- 静态资源映射,指定静态资源请求交由web容器的默认Servlet处理 -->
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.jpg</url-pattern>
    <url-pattern>*.png</url-pattern>
    <url-pattern>*.gif</url-pattern>
    <url-pattern>*.bmp</url-pattern>
    <url-pattern>*.ico</url-pattern>
    <url-pattern>*.js</url-pattern>
    <url-pattern>*.css</url-pattern>
</servlet-mapping>

这种解决办法要求servlet或者说应用服务器必须是Tomcat、Jetty、JBoss、GlassFish中的一种,对Resin、WebLogic、WebSphere不适用,因为Resin的默认servlet的名字是resin-file,WebLogic的默认servlet的名字是FileServlet,WebSphere的默认servlet的名字是resin-file。

2.用<mvc:resources />元素,在spring mvc的配置文件中配置,配置示例:

<mvc:resources location="/image/" mapping="/image/**" />

上面配置表示如果请求是/image开头的,将去/image目录下去找对应的资源。例如,如果请求url是/image/small/111.png,将去image/small目录下找111.png。那么如果要请求css文件,只需再配置css的路径和映射即可。这种解决办法需要提前给各种静态文件分好文件夹,如果文件夹没有指定或是指定错了,就会报找不到文件错误。

3.用<mvc:default-servlet-handler />元素,示例如下:

<mvc:default-servlet-handler />

只需在spring mvc配置文件配置这么一行即可。这将启用spring-mvc-xxx.jar包中的DefaultServletHttpRequestHandler处理器,它会根据当前服务器环境去找对应的默认的servlet处理静态资源的请求,相比于方法1的好处就是,默认的servlet名不是写死的,而是交给spring动态去找的,这样在切换服务器时不用改这里,而且非常简单,就一行。

<mvc:annotation-driven />用法:

这个元素可以自动注册RequestMappingHandlerMappingRequestMappingHandlerAdapter,也就是说,如果在spring mvc配置文件中配置了<mvc:annotation-driven />,就无需在<bean />元素中配置这两个组件了。

此外,在spring mvc配置文件中配置了<mvc:annotation-driven />的话,就可以支持以下注解的使用了:

@RequestBody,用于修饰处理器方法形参,解析请求体参数并赋给方法形参,用于contentType值为application/json且请求参数是json字符串的post请求,需要配合HttpMessageConverter使用

@ResponseBody,用来修饰处理器类或是处理器方法,需要HttpMessageConverter接口来对处理器方法的返回值进行转换,处理器方法返回值类型不同,使用到的具体实现类也不一样,这也是策略模式了。

如果处理器方法返回值是String类型,则要用StringHttpMessageConverter实现类,该类默认的charSet是ISO-8859-1,需要配置其supportedMediaTypes属性值为text/html;charset=UTF-8,这样才能转为UTF-8编码。

如果处理器方法返回的是集合类型或者实体类类型,则具体使用哪个实现类要看应用中引用的处理json的jar包是什么。如果没有引用任何处理json的jar包,这个时候会报错的No converter found for return value of type: class java.util.ArrayList。如果引用了Jackson包,则会用MappingJackson2HttpMessageConverter实现类,该类默认的charSet是UTF-8,最终将向前端返回json。

文件上传:

spring处理文件上传需要使用spring-web-xxx.jar包中的两个接口:MultipartFile接口和MultipartResolver接口。通常我们在spring mvc 配置文件中注册CommonsMultipartResolver实现类,这个实现类是Spring在commons-fileupload基础上开发的,所以此时应用需要引入commons-fileupload-xxx.jar。注意,在注册CommonsMultipartResolver时,必须指定id为multipartResolver,否则处理器方法参数拿不到file数据。

这里要特别注意一点,前端请求的路径不能是/upload,也就说是action属性值不能是upload,否则处理器方法参数也拿不到file数据,这点不能解释。

<form action="uploadFile" method="post" enctype="multipart/form-data">
    <input name="file" type="file"> 
    <input type="submit" value="提交">
</form>

则要在spring mvc配置文件中配置如下:

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>

此外,还可以在配置文件中配置multipartResolver的maxUploadSize属性来限制每次上传文件的总大小(允许一次上传几个文件)或是配置maxUploadSizePerFile属性来限制单个上传文件的大小,但是实测如果上传文件总大小超过限制或是单个文件超过限制的话,后台会报异常,提示The field file exceeds its maximum permitted size,而前端页面直接显示连接被重置,后台又报错,前端提示又不友好,所以我们一般不在这里限制文件上传大小,而是写在处理器方法中。在处理器方法中我们用MultipartFile接口类型参数接收前端传过来的数据,这里的形参名要与前端上传框架的name属性值保持一致,否则接收不到数据,如果不想一致或者不能一致的话,用@RequestParam先接收再赋值即可。

主要用到MultipartFile接口的以下几个方法:

String getOriginalFilename();得到上传的文件的名称,如3月报表.xlsx、姚明.jpg,通过名称我们可以截取出后缀名,可以用后缀名来限制前端传文件的类型

long getSize();得到上传文件的大小,以byte为单位,可以用来限制前端上传文件的大小

transferTo(File dest);将接收到的文件数据写到指定的目标文件中,也就是保存操作

处理器方法示例:

private static final String DEFALTUPLOADPATH = "d:/upload/";

    @RequestMapping(value = "/uploadFile")
    public void uploadFile(MultipartFile file, String pathName, HttpServletRequest request) {
        File tmpFile = null;
        try {
            String fileName = file.getOriginalFilename();
            System.out.println("fileName:" + fileName);
            if (StringUtils.isEmptyOrWhitespaceOnly(fileName)) {
                System.out.println("必须选择文件");
                return;
            }
            // 获取上传文件扩展名
            String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
            fileExt = fileExt.toLowerCase();
            System.out.println("文件类型:" + fileExt);
            if (("jpg").equals(fileExt) || ("jpeg").equals(fileExt) || ("png").equals(fileExt)
                    || ("bmp").equals(fileExt) || ("gif").equals(fileExt)) {
                BufferedImage sourceImg = ImageIO.read(file.getInputStream());
                int imgWidth = sourceImg.getWidth();
                int imgHeight = sourceImg.getHeight();
                System.out.println("上传的图片宽:" + imgWidth);
                System.out.println("上传的图片高:" + imgHeight);
            }
            // 对扩展名进行小写转换

            long fileSize = file.getSize();
            System.out.println("fileSize=" + fileSize);
            // 判断文件是否为空
            File uploadDir = new File(DEFALTUPLOADPATH);
            // 指定的上传目录如果没有的话,就新建此目录
            if (!uploadDir.exists()) {
                uploadDir.mkdir();
            }
            Date now = new Date();
            // 上传文件重命名。因为如果不重命名的话,如果上传的文件名称和目录中某个文件相同,则会覆盖原来的文件,哪怕只是文件名相同,而内容不同,这不行
            String tmpFileName = String.valueOf(now.getTime()) + Math.round(Math.random() * 1000) + "." + fileExt;
            // 文件路径
            String tmpFilePath = DEFALTUPLOADPATH + tmpFileName;
            tmpFile = new File(tmpFilePath);
            file.transferTo(tmpFile);
            // 如果上传的是图片的话,以下代码可以过滤图片尺寸

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (tmpFile.exists()) {
                System.out.println("上传成功,文件路径为:" + tmpFile.getAbsolutePath());
            } else {
                System.out.println("上传失败");
            }
        }
    }

 以上在控制台打印的提示信息,在实际开发时是向前端返回的提示信息,如果想以json返回,只需用@ResponseBody标注此方法,然后返回实体类型、集合类型、Map类型的数据就行。

上面还针对如果上传文件类型是图片的话做了一些特殊处理,如可以获取上传图片的高和宽,进而可以做一些限制。

转载于:https://www.cnblogs.com/koushr/p/5873369.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值