【Handler】Spring MVC控制器详解
Spring MVC控制器
- Handler
- 具体流程
- 项目依赖
- 快速入门
- 参数
- 接收请求参数
- 获取servletAPI
- 返回值
- 注解
- 静态资源访问
- Spring MVC对JSON的支持
- @RequestBody
- @ResponseBody
- 拦截器(filter)
- 创建拦截器
- 配置拦截器
- 实现
Handler
在Spring MVC中,Handler是一个用来处理HTTP请求的对象。它通常是一个控制器(Controller)类中的方法。当用户发送一个请求到服务器时,Spring MVC会根据请求的URL、HTTP方法等信息,找到相应的处理器(Handler)来处理这个请求。
具体流程
- 请求接收: 当一个请求到达Spring MVC应用程序时,首先由前端控制器(DispatcherServlet)接收。
- 处理器映射: DispatcherServlet会根据请求URL,利用HandlerMapping查找对应的Handler。
- 调用处理器: 找到合适的Handler后,DispatcherServlet会调用该Handler来处理请求。这个Handler通常是一个带有@RequestMapping注解的方法。
- 返回视图: Handler处理完请求后,会返回一个ModelAndView对象(或者其他类型的结果),然后由DispatcherServlet根据结果选择合适的视图(View)进行渲染。
简而言之,Spring MVC中的Handler就是负责处理特定HTTP请求的组件,通常是一个控制器方法。它的作用是根据请求的内容执行相应的业务逻辑,并返回适当的响应。
这个过程确保了请求能够根据配置和注解映射到合适的控制器方法,进而处理用户的请求并返回结果。
项目依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
我们使用handler之前,首先需要再pom.xml中引入依赖,“spring-boot-starter-web”,他实际上还是在JavaWeb开发的范畴,所以我们需要引入Spring MVC框架。
快速入门
参数
Handler的参数列表,功能非常强大,主要功能有以下两点:
- 接收请求参数
- 获取servletAPI
接收请求参数
- 简单类型的参数,Spring MVC可以直接帮我们封装成参数列表中声明的类型,比如String、int、double……
//http://localhost:8080/login?username=kobe&password=123
@RequestMapping("/login")
public void login(String username, String password){
System.out.println(username);
System.out.println(password);
}
- 或者也可以直接接收一个Java Bean
//http://localhost:8080/login?name=kobe1&age=23&birthday=2020-02-02
@RequestMapping("/login")
public void login(Person person) {
System.out.println(person.getName());
System.out.println(person.getAge());
}
- 如果请求参数是一个日期,Spring MVC并不能直接封装到Date中,需要设置一下日期格式。
public class User {
private String username;
private String password;
@DateTimeFormat(pattern = "yyyy-MM-dd")//前端到后端 可以把前端string类型转换到后端date类型
//@JsonFormat(pattern = "yyyy-MM-dd")//后端到前端 可以把后端date类型转换到前端string
private Date birthday;
// getters & setters...
}
- 如果请求参数是一个数组类型,我们可以直接通过数组接收。
http://localhost:8080/delSel?ids=1&ids=2 请求参数ids为方法的参数
@RequestMapping("delSel")
public String delSel(Integer[] ids) {
System.out.println(Arrays.toString(ids));
return "index";
}
- 如果想要用集合类型来接收数组参数呢?下面的写法可以么?
//http://localhost:8080/delSel?ids=1&ids=2
@RequestMapping("delSel")
public String delSel(List<Integer> ids) {
System.out.println(ids);
return "index";
}
//不可以,因为方法参数中的数据类型 必须是可实例化的,得有构造方法,List是个接口,没办法构造。但是可以使用ArrayList
//http://localhost:8080/delSel?ids=1&ids=2
@RequestMapping("delSel")
public String delSel(@RequestParam ArrayList<Integer> ids) {
System.out.println(ids);
return "index";
}
//但是必须在参数前面加@RequestParam才可以拿到请求参数
获取servletAPI
可以在Handler的形参中直接使用以下类型:
HttpServletRequest
通过request对象获取请求信息HttpServletResponse
通过response处理响应信息HttpSession
通过session对象得到session中存放的对象
@RequestMapping("servletTest")
public void servletTest(
HttpServletRequest request,
HttpServletResponse response,
HttpSession session
) throws IOException
{
System.out.println(request.getContextPath());
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("网页中文内容");
System.out.println(session.getId());
}
返回值
可以为Handler指定两种种返回值类型:
-
void
如果返回值为
void
的时候,可以在Handler形参上定义request
和response,使用
request或
response指定响应结果 -
String
逻辑视图名称
返回的字符串就是逻辑视图。
@RequestMapping("login2")
public void login2(HttpServletRequest request, HttpServletResponse response) throws IOException {
String username = request.getParameter("username");
System.out.println(username);
response.setContentType("text/html;charset=utf-8");
response.getWriter().println("返回值");
}
@RequestMapping("/login3")
public String login3() {
//return "forward:/login1";//可以请求转发到另一个handler
//return "forward:/index.jsp";//可以请求转发到另一个jsp
return "redirect:/index.jsp";//可以重定向到另一个jsp 注意观察地址栏
}
注解
-
@RequestMapping
- 声明在方法上:
//@RequestMapping(value = "/login1")
//@RequestMapping(path = "/login1")
//@RequestMapping(value = {"/login1", "/login2"})
@RequestMapping(value = "login1",method = RequestMethod.GET)//只有get请求才可以 post请求进不来
public String login(Person person) {
return "index";
}
- 通过`value`属性配置该方法的访问路径
- 通过method属性指定该方法允许的访问方式
- 声明在类上:
@Controller
@RequestMapping("/test")
public class TestController1 {
@RequestMapping("/test1")//test1方法的请求路径变为//http://localhost:8080/test/test1
public String test1() {
System.out.println("Hello SpringMvc test1");
return "index";//返回的就是逻辑视图 请求转发到index.jsp 地址栏没变
}
窄化请求,可以对请求URL进行分类管理,例如:`/person/add`、`/person/list`……
-
@RequestParam
该注解用来标注一个请求参数:
在方法的形参前,可以加可以不加,加了就有特殊含义了
@RequestMapping(value = "/login3")
public String login3(String name) {
System.out.println(name);
return "index";
}
//上述方式在请求 login3 可以不强制传递请求参数,那打印name结果是null
@RequestMapping(value = "/login3")
public String login3(@RequestParam String name) {
System.out.println(name);
return "index";
}
//http://localhost:8080/login3?name=tom
//上述方式在请求 login3 强制必须传递请求参数,那打印name结果就是请求参数传的值
//http://localhost:8080/login3?name1=tom
@RequestMapping(value = "/login3")
public String login3(@RequestParam("name1") String name) {
System.out.println(name);
return "index";
}
//上述方式在请求 login3 的请求参数是name1 指定请求参数的名字
//@RequestParam(value = "name1") 和 @RequestParam(name = "name1") 等同于 @RequestParam("name1")
//http://localhost:8080/login3
@RequestMapping(value = "/login3")
public String login3(@RequestParam(name = "name1", required = false) String name) {
System.out.println(name);
return "index";
}
//required = false,默认是true 代表着必须传递参数,如果设置false代表可以不传递参数
@RequestMapping(value = "/login3")
public String login3(@RequestParam(name = "name1",defaultValue = "zs") String name) {
System.out.println(name);
return "index";
}
//设置默认值,这样required就不需要在写了。
public String login(@RequestParam(value = "username", required = true, defaultValue = "noname") String name)
-
value
:@RequestParam(value="username")
等同于@RequestParam("username")
,对应请求参数的键 -
required
:参数是否必填 -
defaultValue
:设置默认值 -
@PathVariable
将路径的一部分作为请求参数,RESTful的基础。
//http://localhost:8080/findById/1
@RequestMapping("/findById/{id}")
public String findById(@PathVariable Integer id) {
System.out.println(id);
return "index";
}
//请求参数就不可以是?号。参数必须是目录形式。
RESTful就是一个url的编写风格。使用RESTful就不需要用?问号分割请求参数了。
一个严格的RESTful中是不可能存在?的。
一个URl对应一个资源,请求方式确定一个动作。
静态资源访问
springboot中放置静态资源的目录,常用有static和templates目录,只要把静态资源放到这几个目录下,就能直接访问到。
static目录下静态资源默认是可以被访问到的,但是templates目录下资源默认是访问不到的,需要做配置:
spring.web.resources.static-locations=classpath:/templates
也可以直接给所有静态资源添加一个前缀,既可统一拦截,又可统一放开
spring.mvc.static-path-pattern=/res/**
http://localhost:8080/res/login1.html
Spring MVC对JSON的支持
Spring MVC对JSON的⽀持⾮常友好,主要⽤到两个注解:@RequestBody,@ResponseBody
也可以配置为其它的JSON⼯具,⽐如fastjson,⾃学如何配置。
为什么学习Jackson,因为它是SpringMVC默认的,方便配置使用。
@RequestBody
public class User {
private Integer id;
private String username;
private String password;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
c
</head>
<body>
<button id="btn1">发送json</button>
<button id="btn2">接收json</button>
</body>
<script>
$('#btn1').click(function () {
$.ajax({
url: "/login",
type: 'post',
data: '{"username":"zs","password":"20"}',
contentType: 'application/json'
});
});
//在jquery的ajax中,如果没加contentType:"application/json",那么data就应该对应的是json对象。
//反之,如果加了contentType:"application/json",那么ajax发送的就必须是字符串。
//状态码:415, 表示服务器无法处理请求的媒体格式。
//状态码:400,前端传的参数类型或者名称与后台接收参数的实体类的属性类型或者名称不一致,或者后端要一个参数,但是前端没传递
</script>
</html>
Handler部分
@Controller
public class JsonController {
@PostMapping("/login")
public String login(@RequestBody User user) {
System.out.println(user);
return "redirect:/index.html";
}
}
测试:查看控制台是否接受到前端发送过来的json数据。
@ResponseBody
JavaScript部分
<script>
$('#btn2').click(function () {
$.ajax({
url: "/findAll",
type: 'get',
success:function (res){
console.log(res)
}
});
})
</script>
Handler部分
@GetMapping("/findAll")
@ResponseBody
public List<User> findAll() {
List<User> list = new ArrayList<>();
User user = new User();
user.setId(1);
user.setUsername("码上未来");
user.setPassword("codingfuture");
list.add(user);
return list;
}
测试:查看前端页面console页面,是否拿到后端响应过来的数据。
注意:@ResponseBody 注解一定要放在方法上面,标注,不要放到方法返回值处标注。
@ResponseBody 注解也可以放到类上,这样该类内部每个方法都会隐式被标注为@ResponseBody。
FormData
@PostMapping("/person/add")
@ResponseBody
public String add(String username, String password){
System.out.println(username);
System.out.println(password);
return "ok";
}
<script>
let params = new FormData();
params.append("username", "admin");
params.append("password", "123456");
$('#send').click(function () {
console.log("发送数据")
$.ajax({
url: '/person/add',
type: 'post',
data: params,
contentType: false,
processData: false,
});
});
</script>
拦截器(filter)
JavaWeb拦截器(Interceptor)是一种在Web应用程序中用于拦截并处理请求和响应的机制。它在请求到达控制器之前或响应返回给客户端之前执行,通常用于实现如认证、日志记录、性能监控等横切关注点(cross-cutting concerns)。拦截器的主要特点包括:
- 预处理和后处理:可以在请求到达控制器之前进行预处理(如用户身份验证),也可以在响应发送给客户端之前进行后处理(如日志记录)。
- 链式调用:多个拦截器可以按照配置的顺序依次执行,形成一个拦截器链,每个拦截器在执行完之后可以选择继续执行下一个拦截器或终止链的执行。
- 可配置性:拦截器通常可以在配置文件(如web.xml)或通过注解的方式进行配置,决定在哪些请求路径或控制器上应用拦截。
- 独立性:拦截器是独立于业务逻辑的模块,可以复用在多个控制器或路径上,不需要修改具体的业务代码。
在Spring框架中,拦截器通常通过实现HandlerInterceptor接口来定义,其中包括preHandle、postHandle、afterCompletion三个方法,分别用于处理请求前、请求后和请求完成后的逻辑。
创建拦截器
实现HandlerInterceptor
接口
/**
* 创建拦截器
*/
public class LoginInterceptor implements HandlerInterceptor {
//该方法在handler处理请求之前被调用
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
//return true;//拦截失败 通过
return false;//拦截成功,不通过
}
//该方法在当前handler处理之后,也就是Controller方法被调用之后执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) {
}
// 在页面渲染之后进行处理
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
}
}
配置拦截器
//将配置注入容器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
//拦截所有资源
.addPathPatterns("/**")
//将指定资源放行
.excludePathPatterns("/index.html", "/login","/js/**");
}
}
实现
public class LoginInterceptor implements HandlerInterceptor {
/**
* 在接口调用前 执行
*
* @param request
* @param response
* @param handler
* @return true 放行 不拦截 否则...
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
HttpSession session = request.getSession();
Object username = session.getAttribute("username");
if (username != null) {
//放行
return true;
}
Map<String, Object> map = new HashMap<>();
map.put("code", 403);
map.put("data", "list");
map.put("message", "success");
String data = JSON.toJSONString(map);
response.getWriter().println(data);
retrn false;
}
}