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
}
}
}