Controller注解类型

在Spring MVC中,使用org.springframework.stereotype.Controller注解类型声明某类的实例是一个控制器。
RequestMapping注解类型

在基于注解的控制器类中,可以为每个请求编写对应的处理方法。需要使用org.springframework.web.bind.annotation.RequestMapping注解类型将请求与处理方法一一对应。

1.方法级别注解
@RequestMapping(value = "/index/login")
public String login() {}

注解的value属性将请求URI映射到方法,value属性是RequestMapping注解的默认属性,如果就一个value属性,则可省略该属性。可以使用如下URL访问login方法(请求处理方法)。
http://localhost:xxx/yyyy/index/login    
2 类级别注解

@Controller
@RequestMapping("/index")
public class IndexController {
    @RequestMapping("/login")
    public String login() {
        return "login";
    }
    @RequestMapping("/register")
    public String register() {
        return "register";
    }
}


在类级别注解的情况下,控制器类中的所有方法都将映射为类级别的请求。可以使用如下URL访问login方法。
http://localhost:xxx/yyy/index/login
编写请求处理方法

1.请求处理方法中常出现的参数类型
    Servlet API、输入输出流、表单实体类、注解类型、Model等Java类型。
public String login(HttpSession session, HttpServletRequest request) 
2.请求处理方法常见的返回类型
    最常见的返回类型,就是代表逻辑视图名称的String类型。除了String类型外,还有Model、View以及其他任意的Java类型。
public String register(Model model) {}
Controller接收请求参数的常见方式

    Controller接收请求参数的方式有很多种,有的适合get请求方式,有的适合post请求方式,有的两者都适合。
    1.通过实体bean接收请求参数
    通过一个实体bean来接收请求参数,适用于get和post提交请求方式。需要注意的是,bean的属性名称必须与请求参数名称相同。
    2.通过处理方法的形参接收请求参数
    通过处理方法的形参接收请求参数,也就是直接把表单参数写在控制器类相应方法的形参中,即形参名称与请求参数名称完全相同。该接收参数方式适用于get和post提交请求方式。
3.通过@RequestParam接收请求参数
    通过@RequestParam接收请求参数,适用于get和post提交请求方式。通过@RequestParam接收请求参数与“通过处理方法的形参接收请求参数”的区别是:当请求参数与接收参数名不一致时,“通过处理方法的形参接收请求参数”不会报400错误,而“通过@RequestParam接收请求参数”会400错误。
4.通过@ModelAttribute接收请求参数
    @ModelAttribute注解放在处理方法的形参上时,用于将多个请求参数封装到一个实体对象,从而简化数据绑定流程,而且自动暴露为模型数据用于视图页面展示时使用。而“通过实体bean接收请求参数”只是将多个请求参数封装到一个实体对象,并不能暴露为模型数据(需要使用model.addAttribute语句才能暴露为模型数据)
重定向与转发

重定向是将用户从当前处理请求定向到另一个视图(如JSP)或处理请求,以前的请求(request)中存放的信息全部失效,并进入一个新的request作用域;

转发是将用户对当前处理的请求转发给另一个视图或处理请求,以前的request中存放的信息不会失效。

转发是服务器行为,重定向是客户端行为。具体工作流程如下:
转发过程:客户浏览器发送http请求,Web服务器接受此请求,调用内部的一个方法在容器内部完成请求处理和转发动作,将目标资源发送给客户;在这里,转发的路径必须是同一个Web容器下的URL,其不能转向到其他的Web路径上去,中间传递的是自己的容器内的request。在客户浏览器的地址栏中显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。

重定向过程:客户浏览器发送http请求,Web服务器接受后发送302状态码响应及对应新的location给客户浏览器,客户浏览器发现是302响应,则自动再发送一个新的http请求,请求URL是新的location地址,服务器根据此请求寻找资源并发送给客户。在这里location可以重定向到任意URL,既然是浏览器重新发出了请求,则就没有什么request传递的概念了。在客户浏览器的地址栏中显示的是其重定向的路径,客户可以观察到地址的变化。重定向行为是浏览器做了至少两次的访问请求。

在Spring MVC框架中,控制器类中处理方法的return语句默认就是转发实现,只不过实现的是转发到视图。
//转发到一个请求方法(同一个控制器类里,可省略/index/)
return "forward:/index/isLogin";
//重定向到一个请求方法
return "redirect:/index/isRegister";
//转发到一个视图
return "register";

在Spring MVC框架中,不管重定向或转发,都需要符合视图解析器的配置,如果直接重定向到一个不需要DispatcherServlet的资源,如:
return "redirect:/html/my.html";
在Spring MVC配置文件中,需要使用mvc:resources配置:
<mvc:resources location="/html/" mapping="/html/**"></mvc:resources>
在Spring MVC配置类中,需要实现WebMvcConfigurer的接口方法public void addResourceHandlers(ResourceHandlerRegistry registry),示例代码如下:
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/html/**").addResourceLocations("/html/");
    }
应用@Autowired进行依赖注入

    Spring MVC框架本身就是一个非常优秀的MVC框架,它具有一个依赖注入的优点。可以通过org.springframework.beans.factory.annotation.Autowired注解类型将依赖注入到一个属性(成员变量)或方法,如:
@Autowired
public UserService userService;
在Spring MVC中,为了能被作为依赖注入,服务层的类必须使用org.springframework.stereotype.Service注解类型注明为@Service(一个服务)。另外,还需要在配置文件中使用<context:component-scan base-package="基本包"/>元素或者在配置类中使用@ComponentScan("基本包")来扫描依赖基本包。

“登录”和“注册”的业务逻辑处理分离出来,使用Service层实现。
首先,创建service包,在包中创建UserService接口和UserServiceImpl实现类。
其次,将配置类中@ComponentScan("controller")修改如下:
@ComponentScan(basePackages = {"controller","service"})//扫描基本包
最后,修改控制器类UserController,具体代码如下:
public class UserController {
    //将服务层依赖注入到属性userService
    @Autowired
     public UserService userService;
@ModelAttribute

    通过org.springframework.web.bind.annotation.ModelAttribute注解类型,可经常实现如下两个功能:
    1.绑定请求参数到实体对象(表单的命令对象)
public String register(@ModelAttribute("user") UserForm user) {}
    “@ModelAttribute(”user“) UserForm user”语句的功能有两个,一是将请求参数的输入封装到user对象中;一是创建UserForm实例,以“user”为键值存储在Model对象中,与“model.addAttribute(”user“, user)”语句功能一样。如果没有指定键值,即“@ModelAttribute UserForm user”,那么创建UserForm实例时,以“userForm”为键值存储在Model对象中,与“model.addAttribute(”userForm“, user)”语句功能一样。
    2.注解一个非请求处理方法
    被@ModelAttribute注解的控制器的一个非请求处理方法,将在每次调用该控制器类的请求处理方法前被调用。
表单标签库

    表单标签库中包含了可以用在JSP页面中渲染HTML元素的标签。JSP页面使用Spring表单标签库时,必须在JSP页面开头处声明taglib指令,指令代码如下:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
    表单标签库中有form、input、password、hidden、textarea、checkbox、checkboxes、radiobutton、radiobuttons、select、option、options、errors。
表单标签

    表单标签,语法格式如下:
    <form:form modelAttribute="xxx" method="post" action="xxx">
        ……
    </form:form>
    除了具有HTML表单元素属性外,表单标签还有具有acceptCharset、commandName、cssClass、cssStyle、htmlEscape和modelAttribute等属性。

    其中,commandName和modelAttribute属性功能基本一致,属性值绑定一个JavaBean对象。
input标签

    input标签,语法格式如下:
    <form:input path="xxx"/>
    该标签除了cssClass、cssStyle、htmlEscape属性外,还有一个最重要的属性path。path属性将文本框输入值绑定到form backing object的一个属性。
password标签

    password标签,语法格式如下:
    <form:password path="xxx"/>
    该标签与input标签用法完全一致
hidden标签

    hidden标签,语法格式如下:
    <form:hidden path="xxx"/>
    该标签与input标签用法基本一致,只不过它不可显示,不支持cssClass和cssStyle属性。
textarea标签

    textarea基本上就是一个支持多行输入的input元素,语法格式如下:
    <form:textarea path="xxx"/>
   该标签与input标签用法完全一致
checkbox标签

    checkbox标签,语法格式如下:
    <form:checkbox path="xxx" value="xxx"/>
    多个path相同的checkbox标签,它们是一个选项组,允许多选。选项值绑定到一个数组属性。

<form:checkbox path="friends" value="张三"/>张三
<form:checkbox path="friends" value="李四"/>李四
<form:checkbox path="friends" value="王五"/>王五
<form:checkbox path="friends" value="赵六"/>赵六
上述示例代码中复选框的值绑定到一个字符串数组属性friends(String[] friends)。
checkboxes标签

    checkboxes标签渲染多个复选框,是一个选项组,等价于多个path相同的checkbox标签。它有3个非常重要的属性:items、itemLabel和itemValue。
    items:用于生成input元素的Collection、Map或Array。
    itemLabel:items属性中指定的集合对象的属性,为每个input元素提供label。
    itemValue:items属性中指定的集合对象的属性,为每个input元素提供value。
    checkboxes标签语法格式如下:
    <form:checkboxes items="xxx"  path="xxx"/>
<form:checkboxes items="${hobbys}"  path="hobby" />
上述示例代码,是将model属性hobbys的内容(集合元素)渲染为复选框。itemLabel和itemValue缺省情况下,如果集合是数组,复选框的label和value相同;如果是Map集合,复选框的label是Map的值(value),复选框的value是Map的关键字(key)。
radiobutton标签

    radiobutton标签,语法格式如下:
    <form:radiobutton path="xxx" value="xxx"/>
    多个path相同的radiobutton标签,它们是一个选项组,只允许单选。
radiobuttons标签

    radiobuttons标签渲染多个radio,是一个选项组,等价于多个path相同的radiobutton标签。radiobuttons标签,语法格式如下:
    <form:radiobuttons path="xxx" items="xxx"/>
    该标签的itemLabel和itemValue属性与checkboxes标签的itemLabel和itemValue属性完全一样,但只允许单选。

<form:radiobuttons path="xxx" items="xxx"/>
该标签的itemLabel和itemValue属性与checkboxes标签的itemLabel和itemValue属性完全一样,但只允许单选。
select标签

    select标签的选项可能来自其属性items指定的集合,或者来自一个嵌套的option标签或options标签。语法格式如下:
    <form:select path="xxx" items="xxx" /><form:select path="xxx" items="xxx" >
     <option value="xxx">xxx</option>
    </ form:select><form:select path="xxx">
       <form:options items="xxx"/>
    </form:select>
    该标签的itemLabel和itemValue属性与checkboxes标签的itemLabel和itemValue属性完全一样。
options标签

    options标签生成一个select标签的选项列表。因此,需要与select标签一同使用,具体用法参见select标签。
errors标签

    errors标签渲染一个或者多个span元素,每个span元素包含一个错误消息。它可以用于显示一个特定的错误消息,也可以显示所有错误消息。语法如下:
    <form:errors path="*"/><form:errors path="xxx"/>
    其中,“*”表示显示所有错误消息;“xxx”表示显示由“xxx”指定的特定错误消息。
package pojo;

public class UserForm {
    private String uname;// 与请求参数名称相同
    private String upass;
    private String reupass;

    public String getUname() {
        return uname;
    }

    public void setUname(String uname) {
        this.uname = uname;
    }

    public String getUpass() {
        return upass;
    }

    public void setUpass(String upass) {
        this.upass = upass;
    }

    public String getReupass() {
        return reupass;
    }

    public void setReupass(String reupass) {
        this.reupass = reupass;
    }
}
package interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class MyInteceptor implements HandlerInterceptor {
    /**
     * 重写preHandle方法在请求发生前执行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("preHandle方法在请求发生前执行");
        return true;
    }

    /**
     * 重写postHandle方法在请求完成后执行
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle方法在请求完成后执行");
    }
}
package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

import interceptor.MyInteceptor;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "controller", "service" })
public class SpringMVCConfig implements WebMvcConfigurer {
    /**
     * 配置视图解析器
     */
    @Bean
    public InternalResourceViewResolver getViewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/jsp/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

    /**
     * 配置静态资源
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/html/**").addResourceLocations("/html/");
        // addResourceHandler指的是对外暴露的访问路径
        // addResourceLocations指的是静态资源存放的位置
    }

    /**
     * 配置拦截器Bean
     */
    @Bean
    public MyInteceptor myInteceptor() {
        return new MyInteceptor();
    }

    /**
     * 重写addInterceptors方法注册拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInteceptor());
    }

    /**
     * MultipartResolver配置
     */
    @Bean
    public MultipartResolver multipartResolver() {
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        // 设置上传文件的最大值,单位为字节
        multipartResolver.setMaxUploadSize(5400000);
        // 设置请求的编码格式,默认为iso-8859-1
        multipartResolver.setDefaultEncoding("UTF-8");
        return multipartResolver;
    }
}
package config;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class WebConfig implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext arg0) throws ServletException {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMVCConfig.class);// 注册Spring MVC的Java配置类SpringMVCConfig
        ctx.setServletContext(arg0);// 和当前ServletContext关联
        /**
         * 注册Spring MVC的DispatcherServlet
         */
        Dynamic servlet = arg0.addServlet("dispatcher", new DispatcherServlet(ctx));
        servlet.addMapping("/");
        servlet.setLoadOnStartup(1);
    }
}
package service;

import pojo.UserForm;

public interface UserService {
    boolean login(UserForm user);
    boolean register(UserForm user);
}
package service;

import org.springframework.stereotype.Service;

import pojo.UserForm;

//注解为一个服务
@Service
public class UserServiceImpl implements UserService {
    @Override
    public boolean login(UserForm user) {
        if ("zhangsan".equals(user.getUname()) && "123456".equals(user.getUpass()))
            return true;
        return false;
    }

    @Override
    public boolean register(UserForm user) {
        if ("zhangsan".equals(user.getUname()) && "123456".equals(user.getUpass()))
            return true;
        return false;
    }
}
package controller;

import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

import pojo.UserForm;
import service.UserService;

@Controller
@RequestMapping("/user")
public class UserController {
    // 得到一个用来记录日志的对象,这样打印信息的时候能够标记打印的是那个类的信息
    private static final Log logger = LogFactory.getLog(UserController.class);

    // 将服务层依赖注入到属性userService
    @Autowired
    public UserService userService;

    /**
     * 处理登录
     */
    @RequestMapping("/login")
    public String login(UserForm user, HttpSession session, Model model) {
        if (userService.login(user)) {
            session.setAttribute("u", user);
            logger.info("成功");
            return "main";// 登录成功,跳转到main.jsp
        } else {
            logger.info("失败");
            model.addAttribute("messageError", "用户名或密码错误");
            return "login";
        }
    }

    /**
     * 处理注册
     */
    @RequestMapping("/register")
    public String register(@ModelAttribute("user") UserForm user) {
        if (userService.register(user)) {
            logger.info("成功");
            return "login";// 注册成功,跳转到login.jsp
        } else {
            logger.info("失败");
            // 使用@ModelAttribute("user")与model.addAttribute("user", user)功能相同
            // 在register.jsp页面上可以使用EL表达式${user.uname}取出ModelAttribute的uname值
            return "register";// 返回register.jsp
        }
    }

}