基于注解的控制器类
在(一)中,创建第一个应用中,Contoller中使用的是传统的控制器风格,传统的控制器只能编写一个处理方法,
而且需要在springmvc-servlet.xml部署映射,
如果有多个相关的处理方法就需要写多个控制类,部署多次映射,所以,spring mvc 引入了基于注解的控制类,其优点在于:
1、在基于注解的控制器类中,可以有多个处理方法,多个请求,这就可以使相关的处理方法可以写在一个类中,从而减少了控制器类的数量。
2、在基于注解的控制器类中,无需再在配置文件中部署映射,仅需使用RequestMapping 和Controller注释即可。
下面我们将第一个项目中控制器类修改为基于注解的控制器类,我们需要修该以下几个地方:
将两个控制器类修改为一个控制器类:LoginController.java和RegisterController.java合并为IndexController.java:
package controller1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller//这个注解需写在类名上头,表示该类是控制器类
public class IndexController {
@RequestMapping(value="login")//这个注释有两种类型的写法,下面将介绍
public String login() {
return "login";//login代表逻辑视图名称
}
@RequestMapping(value="register")
public String register() {
return "register";
}
}
Springmvc 使用扫描机制扫描所有基于注解的控制器类,使用
<context:component-scan base-package="基于注解的控制器类所在的包"/>
在springmvc-servlet.xml配置文件中注册控制器类所在的包:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 使用扫描机制,扫描控制器类,控制器类都在controller1包及包下 -->
<context:component-scan base-package="controller1"/>
<!-- LoginController控制器类,映射到”/login“ -->
<!-- <bean name="/login" class="controller.LoginController"/> -->
<!-- RegisterController控制器类,映射到”/register“ -->
<!-- <bean name="/register" class="controller.RegisterController"/> -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!-- 后缀 -->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
其中RequestMapping可分为方法级别注释和类级别注释,上面属于方法级别注释,下面将介绍类级别注释
若将请求页面地址修改为如图:
将控制器类修改为类名上上一个RequestMapping,方法名上在写一个RequestMapping,这种方法适用于当请求名特别长的时候,但前缀相同的情况,类似如图Controller类修改为:
package controller1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/index")
public class IndexController {
@RequestMapping(value="/login")
public String login() {
return "login";//login代表逻辑视图名称
}
@RequestMapping(value="/register")
public String register() {
return "register";
}
}
控制器类接受请求参数的常见方式:
1、使用实体bean来接受请求参数:
如下代码所示:
package controller1;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import domain.User;
@Controller
public class IndexController {
@RequestMapping(value="login")
public String login() {
return "login";//login代表逻辑视图名称
}
@RequestMapping(value="register")
//
public String register(User user) {
System.out.println("username:"+user.getUsername());
if("hxz".equals(user.getUsername())&&"123".equals(user.getPassword())) {
return "login";
}else {
return "register";
}
}
}
创建实体bean:
package domain;
public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
修改register.jsp:
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>注册</title>
</head>
<body>
<form action="register" method="post" >
用户名:<input type="text" name="username" size=6>
密码:<input type="password" name="password" size=6>
确认密码:<input type="password" size=6>
<input type="submit" value="注册">
</form>
</body>
</html>
修改以后可以成功跳转,控制台打印如下:
为什么会出现刚开始会null?
因为我在第一次发请求时:
请求的时register,
此处提交请求时还没有输入内容,所以当然是null啦,
在后面的注册页面发的请求仍然是register,
此处提交请求时已输入用户名和密码,所以会输出。
2、通过形参接受请求参数:
public String register(String username,String password) {
System.out.println("username:"+username);
if("hxz".equals(username)&&"123".equals(password)) {
return "login";
}else {
return "register";
}
3,通过@PathVariable接收URL中的请求参数:
@RequestMapping(value="/two/{uname}/{upass}", method=RequestMethod.GET)
public String register(@PathVariable String uname,@PathVariable String upass, Model model) {
if("zhangsan".equals(uname)
&& "123456".equals(upass))
return "login";//注册成功,跳转到login.jsp
else{
//在register.jsp页面上可以使用EL表达式取出model的uname值
model.addAttribute("uname", uname);
return "register";//返回register.jsp
}
}
4、通过HttpServletRequest接收请求参数:
@RequestMapping("/register")
public String register(HttpServletRequest request, Model model) {
String uname = request.getParameter("username");
String upass = request.getParameter("password");
if("zhangsan".equals(uname)
&& "123456".equals(upass))
return "login";//注册成功,跳转到login.jsp
else{
//在register.jsp页面上可以使用EL表达式取出model的uname值
model.addAttribute("uname", uname);
return "register";//返回register.jsp
}
}
5、通过@RequestParam接收请求参数
/**
* 通过@RequestParam接收请求参数
*/
public String register(@RequestParam String uname, @RequestParam String upass, Model model) {
if("aa".equals(uname)
&& "123".equals(upass))
return "login";//注册成功,跳转到login.jsp
else{
//在register.jsp页面上可以使用EL表达式取出model的uname值
model.addAttribute("uname", uname);
return "register";//返回register.jsp
}
}
}
由于前面提到过,两次请求处理方法都是register,但上面这个处理方法写出来以后,要求带有参数才能用这个处理方法,所以需要为第一次请求再重写一个 处理方法,否则会404.
@RequestMapping("/register")
public String register(){
return "register";
}
6、通过@ModelAttribute接收请求参数
通过@ModelAttribute注解在处理方法上时,可以将多个请求参数封装在一个实体对象中,简化数据绑定流程,自动暴露为模型数据,于视图页面展示时使用
@RequestMapping("/register1")
public String register(@ModelAttribute("user") UserForm user) {
if("zhangsan".equals(user.getUname())
&& "123456".equals(user.getUpass()))
return "login";//注册成功,跳转到login.jsp
else{
//使用了@ModelAttribute("user")和使用了model.addAttribute功能相同
//model.addAttribute("uname", user.getUname());
return "register";//返回register.jsp
}
}
重定向与转发:
区别:
- 重定向是将用户当前请求重定向到另一个请求或视图,其中request储存的信息全部失效,并进入到一个心得request作用域,而转发是将用户当前请求转发到另一个请求或视图,request作用域不会失效。
- 重定向是客户端行为,客户发送请求,服务器发回302状态码响应,并将新的location发送给客户端,浏览器发现是302,便对新的location发送请求;而转发是服务器行为,在同一个web容器内实现,所以只能转发到同一容器下的url。
- 重定向,地址栏会发生变化;转发地址栏不会发生变化。
- 转发相当于一次请求,而重定向相当于两次请求。
在spring MVC 框架中 return语句中默认是转发请求
以下代码展示了这两种区别:
public class IndexController {
@RequestMapping("/login")
public String login() {
/*不管重定向或转发,都需要符合视图解析器的配置,如果直接跳转到一个不需要dispatcher servlet的资源,
* 需要使用mvc:resources配置,
如:<mvc:resources location="/html/" mapping="/html/**"></mvc:resources> )
return "forward:/html/my.html";*/
//转发到一个请求方法(同一个控制器类里,可省略/index/)
//return "forward:/index/isLogin";
return "login";
}
@RequestMapping("/isLogin")
public String isLogin() {
//重定向到一个请求方法
return "redirect:/index/isRegister";
}
@RequestMapping("/isRegister")
public String isRegister() {
//转发到一个视图,jsp页面
return "register";// 默认是转发打开*.jsp
}
@RequestMapping("/register")
public String register() {
return "register";
}
}
转发地址栏:
重定向地址栏:
应用@Autowired和@Service进行依赖注入
依赖注入又称为控制反转,框架自动对要使用的bean对象进行实例化,并不需要你手动实现,控制权由程序员交给了容器。
在上面的实验中会发现并没有MVC中M层的体现,是因为C层即充当了C也充当了M层,这样设计并不合理,所以需要分离出来。
创建一个service包,包中写一个接口,并且实现这个接口:
代码:
package service;
import domain.UserForm;
public interface UserService {
boolean login(UserForm user);
boolean register(UserForm user);
}
package service;
import org.springframework.stereotype.Service;
import domain.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;
}
}
在实现类的上方使用一个@service便将该类注明为一个服务
还需要在spring.xml配置文件中注册这个包
那么Controller中两个处理方法就可以简化了,可以改为:
@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", "用户名或密码错误");
// 等价于 request.setAttribute("k", Object);
return "login";
}
}
@RequestMapping("/register")
public String register(){
return "register";
}
/**
*处理注册
*/
@RequestMapping("/register1")
public String register(@ModelAttribute("user") UserForm user) {
if(userService.register(user)){
logger.info("成功");
return "login";
}else{
logger.info("失败");
return "register";
}
}
@ModelAttribute:
1、绑定请求参数到实体对象
在一个处理方法中参数中(@ModelAttribute(“user”)UserForm user)和(@ModelAttribute UserForm user),这两种语句都会创建一个UserForm类型的实例对象,但在model中存储的键值不同,第一种为user,因为已指明,第二种为userForm,因为没有指明键值,所以会以类名第一个首字母小写,转化为键值存储。
2、注解一个非请求的处理方法
当方法上以@ModelAttribute注明时,该方法将在Controller类的请求处理方法之前被调用。相当于一个过滤器的作用。
示例代码:
@ModelAttribute
public void isLogin(HttpSession session) throws Exception {
if(session.getAttribute("user") == null){
throw new Exception("没有权限");
}
}