SpringMVC入门

SpringMVC处理流程:

(1)DispatcherServlet截获客户端请求。

(2)DispatcherServlet解析URL,通过查找处理器映射器(HandlerMapping)找到对应的处理器对象(Handler),以及这个对象关联的拦截器(Interceptor),这些对象被封装在一个HandlerExecutionChain对象里面。

(3)DispatcherServlet将请求请求交给合适的适配器处理。适配器的主要功能是帮助DispatcherServlet调用Handler实际的处理请求的方法。Spring官方文档中是这样说的:

Helps the DispatcherServlet to invoke a handler mapped to a request regardless of the handler is actually invoked. For example, invoking an annotated controller requires resolving various annotations. Thus the main purpose of a HandlerAdapter is to shield the DispatcherServlet from such details.

为了调用指定的请求处理方法,还需要对客户端发送的数据进行转换,比如转换为Java的类型以及POJO。

(4)Handler处理完请求后向DispatcherServlet返回一个ModelAndView对象,这个对象包含视图名和模型数据。

(5)DispatcherServlet根据ModelAndView对象,选择一个合适的视图解析器(ViewResolver)渲染视图。

(6)DispatcherServlet将渲染的视图结果返回给客户端。


1. 开发一个控制器

1.1 配置springmvc.xml

(1)打开Spring Bean的自动扫描;

(2)配置视图解析器:JSP页面的话使用InternalResourceViewResolver,Velocity模板使用VelocityViewResolver;并定义好前缀和后缀;

    <!-- spring可以自动去扫描base-pack下面的包或者子包下面的java文件,
        如果扫描到有Spring的相关注解的类,则把这些类注册为Spring的bean -->
    <context:component-scan base-package="org.fkit.controller"/>

    <!-- 视图解析器  -->
    <bean id="viewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 前缀 -->
        <property name="prefix">
            <value>/WEB-INF/content/</value>
        </property>
        <!-- 后缀 -->
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>

1.2 定义控制器

@Controller
public class DeptController {

    /**
     * 自动注入UserService
     * */
    @Autowired
    @Qualifier("hrmService")
    private HrmService hrmService;

    /**
     * 处理/login请求
     * */
    @RequestMapping(value="/dept/selectDept")
     public String selectDept(Model model,Integer pageIndex,
             @ModelAttribute Dept dept){
        PageModel pageModel = new PageModel();
        if(pageIndex != null){
            pageModel.setPageIndex(pageIndex);
        }
        /** 查询用户信息     */
        List<Dept> depts = hrmService.findDept(dept, pageModel);
        model.addAttribute("depts", depts);
        model.addAttribute("pageModel", pageModel);
        return "dept/dept";
    }

    /**
     * 处理删除部门请求
     * @param String ids 需要删除的id字符串
     * @param ModelAndView mv
     * */
    @RequestMapping(value="/dept/removeDept")
     public ModelAndView removeDept(String ids,ModelAndView mv){
        // 分解id字符串
        String[] idArray = ids.split(",");
        for(String id : idArray){
            // 根据id删除部门
            hrmService.removeDeptById(Integer.parseInt(id));
        }
        // 设置客户端跳转到查询请求
        mv.setViewName("redirect:/dept/selectDept");
        // 返回ModelAndView
        return mv;
    }
}

(1)@Controller声明一个控制器类。

(2)@RequestMapping标识请求路径对应的处理方法。

(3)一个Controller往往需要调用一个Service对象来完成业务逻辑,所以使用@Autowired + @Qualifier(“hrmService”)的方式注入进来。

(4)处理方法参数中既可以使用ModelAndView也可以使用Model对象。

(5)@ModelAttribute注解的用法有多种,最常见的就是用于注释一个参数:

     public String selectDept(Model model,Integer pageIndex,
             @ModelAttribute Dept dept){

Spring框架会将页面提交的表达数据按照名称封装到Dept对象中,@ModelAttribute的作用就是以””dept”为key,将dept这个对象添加到Model对象中。

1.2.1 接受请求的参数
@RequestParam

按名称接收Query Parameters或表单数据,required默认是true,也就是如果没有这个参数会报错。

    @RequestMapping(value="/login")
     public ModelAndView login(@RequestParam("loginname") String loginname,
             @RequestParam("password") String password,
             HttpSession session,
             ModelAndView mv){
@RequestParam(value="password", required=true, defaultValue="admin")
@PathVariable

和@RequestMapping结合使用,接收Path变量:

    @RequestMapping(value="/{formName}")
     public String loginForm(@PathVariable String formName){
        // 动态跳转页面
        return formName;
    }
@SessionAttributes

只能声明在类上,不能声明在方法上。

标识将Model中的特定属性放入到HttpSession对象中。默认情况下Spring MVC将模型中的数据存储到request域中。当一个请求结束后,数据就失效了。如果要跨页面使用。那么需要使用到session。而@SessionAttributes注解就可以使得模型中的数据复制一份到session域中。

@SessionAttributes(value={"names"},types={Integer.class})
@Controller
public class Test {
    @RequestMapping("/test")
    public String test(Map<String,Object> map){
        map.put("names", Arrays.asList("caoyc","zhh","cjx"));
        map.put("age", 18);
        return "hello";
    }
}

1.2.2 处理表单数据
application/x-www-form-urlencoded

HTML中的简单表单控件如下:

<form action="user/login.do" method="get" >  
    用户名:<input type="text" name="username"><br>  
    密码:<input type="text" name="password"><br>  
    <input type="submit" value="登录"/>  
</form>  

application/x-www-form-urlencoded 是常用的表单发包方式,普通的表单提交,或者js发包,默认都是通过这种方式。

(1)GET请求发送表单

当HTTP Method为GET时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2…),然后把这个字串append到url后面,用?分割,加载这个新的url:

http://localhost:8080/springmvc/user/login.do?username=xiaoming&password=123456789

(2)POST请求发送表单

当HTTP Method为POST时,浏览器把form数据封装到http body中,然后发送到服务器:

POST http://localhost:8080/springmvc/user/login.do HTTP/1.1  
Host: localhost:8080  
Connection: keep-alive  
Content-Length: 33  
Cache-Control: max-age=0  
Origin: http://localhost:8080  
Upgrade-Insecure-Requests: 1  
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36  
Content-Type: application/x-www-form-urlencoded  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8  
Referer: http://localhost:8080/springmvc/  
Accept-Encoding: gzip, deflate  
Accept-Language: zh-CN,zh;q=0.8  

username=xiaoming&password=123456789

@RequestParam和@ModelAttribute,还有@RequestBody都可以处理application/x-www-form-urlencoded类型的表单。


multipart/form-data

如果没有 type=file 的控件,form表单会自动form的enctype属性为编码方式默认的 application/x-www-form-urlencoded。

如果有 type=file 的话,就要用到 multipart/form-data 了。浏览器会把整个表单以控件为单位分割,并为每个部分加上Content-Disposition(form-data或者file)、Content-Type(默认为text/plain)、name(控件name)等信息,并加上分割符(boundary)。

需要上传附件时,必须为”multipart/form-data”,而且Method一定是POST:

<form action="user/login.do" method="post" enctype="multipart/form-data">  
    用户名:<input type="text" name="username"><br>  
    密码:<input type="text" name="password"><br>  
    上传文件:<input type="file" name="uploadFile"/><br>  
    <input type="submit" value="登录"/>  
</form>  

提交表单时,Http请求协议如下:

POST http://localhost:8080/springmvc/user/login.do HTTP/1.1  
Host: localhost:8080  
Connection: keep-alive  
Content-Length: 400  
Cache-Control: max-age=0  
Origin: http://localhost:8080  
Upgrade-Insecure-Requests: 1  
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36  
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarykALcKBgBaI9xA79y  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8  
Referer: http://localhost:8080/springmvc/  
Accept-Encoding: gzip, deflate  
Accept-Language: zh-CN,zh;q=0.8  

------WebKitFormBoundarykALcKBgBaI9xA79y  
Content-Disposition: form-data; name="username"  

xiaoming 
------WebKitFormBoundarykALcKBgBaI9xA79y  
Content-Disposition: form-data; name="password"  

123456789  
------WebKitFormBoundarykALcKBgBaI9xA79y  
Content-Disposition: form-data; name="uploadFile"; filename="file.txt"  
Content-Type: text/plain  

文件中的内容       
------WebKitFormBoundarykALcKBgBaI9xA79y--  

——WebKitFormBoundarykALcKBgBaI9xA79y– 是分割分。

对于有上传文件的multipart/form-data表单数据,@RequestParam和@ModelAttribute都可以处理,但是@RequestBody不能处理。


1.2.3 处理上传文件

(1)使用@RequestParam绑定MultipartFile文件:

    // 上传文件会自动绑定到MultipartFile中
     @RequestMapping(value="/upload",method=RequestMethod.POST)
     public String upload(HttpServletRequest request,
            @RequestParam("description") String description,
            @RequestParam("file") MultipartFile file) throws Exception{
       ......
     }

(2)希望整个表单数据直接被Spring转换为一个对象:

User(domain):

public class User implements Serializable{
    private String username;
    private MultipartFile image;

    // 省略构造器和setter/getter
}

FileUplodController:

     @RequestMapping(value="/register")
     public String register(HttpServletRequest request,
             @ModelAttribute User user,
             Model model)throws Exception{
        System.out.println(user.getUsername());
        // 如果文件不为空,写入上传路径
        if(!user.getImage().isEmpty()){
            // 上传文件路径
            String path = request.getServletContext().getRealPath(
                    "/images/");
            // 上传文件名
            String filename = user.getImage().getOriginalFilename();
            File filepath = new File(path,filename);
            // 判断路径是否存在,如果不存在就创建一个
            if (!filepath.getParentFile().exists()) { 
                filepath.getParentFile().mkdirs();
            }
            // 将上传文件保存到一个目标文件当中
            user.getImage().transferTo(new File(path+File.separator+ filename));
            // 将用户添加到model
            model.addAttribute("user", user);
            return "userInfo";
        }else{
            return "error";
        }
    }

@RequestParam只能获取表单数据中的一条属性,@ModelAttribute既可以获取一个属性,也可以获取整个对象。


1.2.4 @RequestBody

处理POST请求时,请求中的Body一般有一下几种格式:

  • application/x-www-form-urlencoded:@RequestParam、@ModelAttribute、@RequestBody都可以处理;
  • multipart/form-data:@RequestParam、@ModelAttribute可以,@RequestBody不行;
  • application/json和application/xml:必须使用@RequestBody;

前面已经介绍过了,目前最常用和高效的开发模型下,我们会尽量使用Spring的注解和默认配置。在配置处理器适配器的时候,选择RequestMappingHandlerAdapter一般就够用了。在数据转换方法,这个适配器默认装载了一下HTTPMessageConverter:

  • StringHTTPMessageConverter:字符串,响应媒体类型是text/plain;
  • ByteArrayHTTPMessageConverter:读写二进制数据,application/octet-stream;
  • SourceHTTPMessageConverter
  • XmlAwareHTTPMessageConverter:xml;

尤其在使用mvc配置的时候,<mvc:annotation-driven>会自动注册RequestMappingHandlerMapping与RequestMappingHandlerAdapter两个Bean,这是Spring MVC为@Controller分发请求所必须的,并提供以下支持:

  • 数据绑定
  • @NumberFormat、@DateTimeFormat
  • @Valid
  • JAXB读写XML
  • JSON(默认Jackson)

如果要处理Json数据格式,需要先取消message-converters的默认配置,然后在converters列表中加入MappingJackson2HTTPMessageConverter。这个转换器是Spring提供的,看名字就知道依赖的是Jackson库,但是现在用得更普遍的是fastjson,如下配置:

    <!-- spring可以自动去扫描base-pack下面的包或者子包下面的java文件,
        如果扫描到有Spring的相关注解的类,则把这些类注册为Spring的bean -->
    <context:component-scan base-package="org.fkit.controller"/>
    <!-- 使用默认的Servlet来响应静态文件 -->
    <mvc:default-servlet-handler/>
    <!-- 设置配置方案 -->
    <mvc:annotation-driven>
        <!-- 设置不使用默认的消息转换器 -->
        <mvc:message-converters register-defaults="false">
            <!-- 配置Spring的转换器 -->
            <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
            <!-- 配置fastjson中实现HttpMessageConverter接口的转换器 -->
            <bean id="fastJsonHttpMessageConverter" 
                class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <!-- 加入支持的媒体类型:返回contentType -->
                <property name="supportedMediaTypes">
                    <list>
                        <!-- 这里顺序不能反,一定先写text/html,不然ie下会出现下载提示 -->
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
@Controller
@RequestMapping("/json")
public class BookController {

    private static final Log logger = LogFactory.getLog(BookController.class);

    // @RequestBody根据json数据,转换成对应的Object
    @RequestMapping(value="/testRequestBody")
    public void setJson(@RequestBody Book book,
            HttpServletResponse response) throws Exception{
        // JSONObject-lib包是一个beans,collections,maps,java arrays和xml和JSON互相转换的包。
        // 使用JSONObject将book对象转换成json输出
        logger.info(JSONObject.toJSONString(book));
        book.setAuthor("肖文吉");
        response.setContentType("text/html;charset=UTF-8");
        // 将book对象转换成json写出到客户端
        response.getWriter().println(JSONObject.toJSONString(book));
    }
}

上面Controller的代码注意三点:

(1)@RequestBody 是Jason数据;

(2)JSONObject.toJSONString(book)是fastjson中的方法;

(3)response.getWriter().println(JSONObject.toJSONString(book)) 写出json到客户端;


2. 数据转换与校验

2.1 数据转换与格式化

客户端提交的数据往往是字符串,Spring框架需要将这些数据转换与绑定为对应的Java参数,比如日期Date、货币等。Spring早期提供了转换器实现这一功能,用户自定义的转化器只需要实现Converter<T>,然后在ConversionServiceFactoryBean中注册这个转化器即可。

但是转换器不提供输入输出的信息格式化工作,所以Spring 3.0以后引入了格式化器Formatter<T>,使用方法和转换器类似。既可以转换输入的数据,也可以对输出格式化。用户自定义的转化器只需要实现Formatter<T>,然后在FormattingConversionServiceFactoryBean中注册这个转化器即可。更方便的是,我们需要数据转化的场景多是针对日期或数字,所以Spring提供@DataTimeFormatter@NumberFormatter注解,可以标注在领域模型需要的属性上。

public class User implements Serializable{
    // 日期类型
    @DateTimeFormat(pattern="yyyy-MM-dd")
    private Date birthday;
    // 正常数字类型
    @NumberFormat(style=Style.NUMBER, pattern="#,###")  
    private int total;  
    // 百分数类型
    @NumberFormat(style=Style.PERCENT)  
    private double discount;  
    // 货币类型
    @NumberFormat(style=Style.CURRENCY)  
    private double money;
  .....
}

2.2 数据校验

2.2.1 Validation框架

这里写图片描述

/ 实现Spring的Validator接口
@Repository("userValidator")
public class UserValidator implements Validator {

    // 该校验器能够对clazz类型的对象进行校验。
    @Override
    public boolean supports(Class<?> clazz) {
        // User指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口。
        return User.class.isAssignableFrom(clazz);
    }

    // 对目标类target进行校验,并将校验错误记录在errors当中
    @Override
    public void validate(Object target, Errors errors) {
        /**
        使用ValidationUtils中的一个静态方法rejectIfEmpty()来对loginname属性进行校验,
        假若'loginname'属性是 null 或者空字符串的话,就拒绝验证通过 。
        */
        ValidationUtils.rejectIfEmpty(errors, "loginname", null, "登录名不能为空");  
        ValidationUtils.rejectIfEmpty(errors, "password", null, "密码不能为空");  
        User user = (User)target;
        if(user.getLoginname().length() > 10){
            // 使用Errors的rejectValue方法验证
            errors.rejectValue("loginname", null, "用户名不能超过10个字符");
        }
        if(user.getPassword() != null 
                && !user.getPassword().equals("") 
                && user.getPassword().length() < 6){
            errors.rejectValue("password", null, "密码不能小于6位");
        }
    }

}
@Controller
public class UserController{
    private static final Log logger = LogFactory.getLog(UserController.class);

    // 注入UserValidator对象
    @Autowired
    @Qualifier("userValidator")
    private UserValidator userValidator;

    @RequestMapping(value="/{formName}")
     public String loginForm(
             @PathVariable String formName,
             Model model){
        User user = new User();
        model.addAttribute("user",user);
        // 动态跳转页面
        return formName;
    }

     @RequestMapping(value="/login",method=RequestMethod.POST)
     public String login(
             @ModelAttribute User user,
             Model model,
             Errors errors) {
         logger.info(user);
         model.addAttribute("user", user);
         // 调用userValidator的验证方法
         userValidator.validate(user, errors);
         // 如果验证不通过跳转到loginForm视图
         if(errors.hasErrors()){
             return "loginForm";
         }
         return "success";
     }
}
2.2.2 JSR 303注解

JSR 303标准有两个实现,一个是Hibernate Validator,一个是Apache Bval,我们推荐前者。

这里写图片描述

这里写图片描述

这里写图片描述

领域对象上加注解:

public class User implements Serializable{
    @NotBlank
    private String loginname;

    @NotBlank
    @Length(min=6,max=8)
    private String password;

    @NotBlank
    private String username;

    @Range(min=15, max=60)
    private int age;

    @Email
    private String email;

    @DateTimeFormat(pattern="yyyy-MM-dd")
    @Past
    private Date birthday;

    @Pattern(regexp="[1][3,8][3,6,9][0-9]{8}")
    private String phone;
  ....
}

Controller上使用@Valid注解对提交的数据进行校验,后面跟着Errors对象保存校验信息:

     @RequestMapping(value="/login",method=RequestMethod.POST)
     public String login(
             @Valid @ModelAttribute User user,
             Model model,
             Errors errors) {
         logger.info(user);
         // 如果验证不通过跳转到loginForm视图
         if(errors.hasErrors()){
             return "loginForm";
         }
         return "success";
     }

3. 拦截器

开发一个拦截器的步骤很简单:

(1)实现HandlerInterceptor接口,或者继承HandlerInterceptorAdapter;

  • preHandle:返回false则请求结束;先声明的Interceptor先执行它的preHandle;
  • postHandle:可以对Controller返回的ModelAndView进行处理;先声明的Interceptor后执行它的postHandle;
  • afterCompletion:主要作用是资源清理;

(2)在Springmvc.xml文件中声明这个拦截器并指明拦截路径

拦截器:

public class AuthorizationInterceptor  implements HandlerInterceptor {

    // 不拦截"/loginForm"和"/login"请求
    private static final String[] IGNORE_URI = {"/loginForm", "/login"};

     /** 
     * 该方法将在整个请求完成之后执行, 主要作用是用于清理资源的,
     * 该方法也只能在当前Interceptor的preHandle方法的返回值为true时才会执行。 
     */  
    @Override
    public void afterCompletion(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception exception)
            throws Exception {
        System.out.println("AuthorizationInterceptor afterCompletion --> ");

    }
    /** 
     * 该方法将在Controller的方法调用之后执行, 方法中可以对ModelAndView进行操作 ,
     * 该方法也只能在当前Interceptor的preHandle方法的返回值为true时才会执行。 
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler, ModelAndView mv) throws Exception {
        System.out.println("AuthorizationInterceptor postHandle --> ");

    }

     /** 
     * preHandle方法是进行处理器拦截用的,该方法将在Controller处理之前进行调用,
     * 该方法的返回值为true拦截器才会继续往下执行,该方法的返回值为false的时候整个请求就结束了。 
     */  
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler) throws Exception {
        System.out.println("AuthorizationInterceptor preHandle --> ");
        // flag变量用于判断用户是否登录,默认为false 
        boolean flag = false; 
        //获取请求的路径进行判断
        String servletPath = request.getServletPath();
        // 判断请求是否需要拦截
        for (String s : IGNORE_URI) {
            if (servletPath.contains(s)) {
                flag = true;
                break;
            }
        }
        // 拦截请求
        if (!flag){
            // 1.获取session中的用户 
            User user = (User) request.getSession().getAttribute("user");
            // 2.判断用户是否已经登录 
            if(user == null){
                // 如果用户没有登录,则设置提示信息,跳转到登录页面
                 System.out.println("AuthorizationInterceptor拦截请求:");
                 request.setAttribute("message", "请先登录再访问网站");
                 request.getRequestDispatcher("loginForm").forward(request, response);
            }else{
                // 如果用户已经登录,则验证通过,放行
                 System.out.println("AuthorizationInterceptor放行请求:");
                 flag = true;
            }
        }
        return flag;
    }
}

springmvc.xml:

   <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/*"/>
            <!-- 使用bean定义一个Interceptor,直接定义在mvc:interceptors根下面的Interceptor将拦截所有的请求 -->  
            <bean class="org.fkit.interceptor.AuthorizationInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值