0
点赞
收藏
分享

微信扫一扫

Spring MVC(三)- 处理器与注解

 Spring MVC 用@Controller及@RestController 注解来标志(自动扫描并注册成bean)该类是一个控制器容器类,在该类下,使用@RequestMapping及其扩展注解来定义处理器。使用注解,可以定义请求的映射、请求的输入、异常处理等。

1 映射请求

使用@RequestMapping 注解来将不同的请求映射到对应的处理器方法上,可以通过URL、请求方法、请求参数、请求头及媒体类型来作匹配。基于其的扩展注解是针对了具体的请求方法做匹配:@GetMapping、@PostMapping等。

1.1 URI 模式

可以使用通配符来匹配请求的URL。

模式

描述

例子

?

匹配一个任意字符。

“/user/get?”

匹配:“/user/get1”

不匹配:“/user/get12”

*

匹配0个或多个任意字符。

“/user/*/get”

匹配:“/user/g1/get”

不匹配:“/user/g1/g2/get”

**

匹配0或多个路径段,直到路径段结束。

“/user/*/get”

匹配:“/user/get”、

“/user/g1/g2/get”

{name}

匹配一个路径段,并捕获它赋值到变量“name”。

“/user/{id}/info”

匹配:“/user/1234/info”,变量id 值为1234。

不匹配:“/user/12/34/info”

{name:[a-z]+}

匹配一个符合表达式段路径段、并捕获它赋值到变量“name”。

“/user/{id:\\d+}/info”,

匹配:“/user/1234/info”

不匹配:“/user/12a/info”

表 @RequestMapping 支持的匹配模式

上面捕获并赋值到变量,可以在处理器方法参数中,通过@PathVariable注解来获取。

@GetMapping("/user/{id:\\d+}/info")
public Pet findPet(@PathVariable Long id) {
    // 请求 /user/123/info,则该方法参数id的值为123
}

1.1.1 RFD(反射型文件下载)漏洞

是一个针对客户端的攻击漏洞,主要发生在响应中设置了“Content-Disposition”头,并且该头的filename属性是由用户请求输入的。这是因为Spring 并没有对用户请求传入的文件名进行严格检测。为了防止该漏洞,开发者应该:

  1. 对filename 参数进行严格的验证和过滤,确保它只包含合法的字符,并且不会造成混淆。
  2. 使用白名单验证,只允许预定义的、安全的文件名格式。
  3. 避免将用户提供的输入直接用作文件名。
@Controller 
public class FileDownloadController { 

    // 假设我们有一个文件路径 
    private static final String FILE_PATH = "path/to/your/file.txt"; 

    @GetMapping("/download") 
    public void downloadFile(@RequestParam String filename, HttpServletResponse response) throws IOException { 

        // 这里故意没有验证和过滤filename参数,导致RFD漏洞 
        Path filePath = Paths.get(FILE_PATH); 
        String contentType = Files.probeContentType(filePath); 
        response.setContentType(contentType); 

        // 使用用户提供的filename作为Content-Disposition中的文件名 
        String headerValue = String.format("attachment; filename=\"%s\"", filename); 
        response.setHeader("Content-Disposition", headerValue); 
        // 将文件内容写入响应输出流 
        Files.copy(filePath, response.getOutputStream()); 
        response.flushBuffer(); 
    } 
}
上面代码中,假如用户的请求是:http://localhost:8080/download?filename=malicious.txt%0D%0AContent-Type:%20text/html%0D%0A%0A<script>alert('Your computer has been compromised!')</script>
那么将可能导致客户端浏览器接收到一个格式不正确的响应,并且可能会执行注入的脚本。

1.2 窄化请求

@RequestMapping 注解还可以通过Media Types、请求参数及请求头等方式来窄化请求匹配。

1.2.1 Media Types

其属性consumes表示请求头的Content-Type,而produces表示响应的Content-Type,由请求头的Accept属性决定。

@PostMapping(path = "/pets", consumes = "application/json",produces = "application/json")
public Object addPet(@RequestBody Pet pet) {
    // 表示该请求头的Content-Type 包含 application/json,Accept包含application/json
}

1.2.2 请求参数与请求头

其属性params 用来表示匹配的请求参数值,而headers 则表示匹配的请求头属性值。

@GetMapping(path = "/pets/{petId}", params = "myParam=myValue", headers = "Content-Type =application/json ")
public void findPet(@PathVariable String petId) {
  // .请求参数中,参数名为myParam的值要为myValue,请求头的Content-Type属性为//application/json(如果是这个请求头属性,则最好用consumes属性来指定)
}

1.3 其他请求方法

HEAD

与GET请求类似,但是响应只返回HTTP头信息,不返回实际的响应体。请求会被服务器处理。用于请求资源的元数据,常用于检查资源是否存在、更新状态或其他头部信息。

OPTIONS

用于获取目标资源所支持的请求方法。常用于跨域请求,在发送可能被视为有风险的请求(如POST或PUT)之前,浏览器可能会先发出一个OPTIONS请求来检查服务器是否允许这种请求。

表 HEAD 与 OPTION 请求

1.4 动态注册处理器

可以通过动态编程方式来动态地注册映射器。

@Autowired
public void setHandlerMapping(RequestMappingHandlerMapping mapping, DynamicHandler handler) throws NoSuchMethodException {
    RequestMappingInfo mappingInfo = RequestMappingInfo.paths("/dynamic").methods(RequestMethod.GET).build();
    Method method = DynamicHandler.class.getMethod("showInfo"); // 处理方法
    mapping.registerMapping(mappingInfo,handler,method);
}

2 处理器方法

@RequestMapping 注解方法时,可以为这个方法定义灵活的签名。选择一些列受支持的参数类型和返回值类型。

常见的方法类型参数有:

  1. HttpServletRequest 和 HttpServletResponse 对象。
  2. @PathVariable、@RequestParam、@RequestHeader等注解标注的参数,用于从请求中提取信息。
  3. @ModelAttribute注解标记的参数,用于绑定请求参数到模型对象。
  4. java.util.Map或org.springframework.ui.Model对象,用于添加模型属性等。

常见的返回值类型有:

  1. 字符串(通常表示逻辑视图名)。
  2. ModelAndView对象(包含视图和模型数据)。
  3. ResponseEntity对象(用于构建HTTP响应)。
  4. @ResponseBody 注解标记的对象(Spring MVC会将其序列化为JSON或XML并返回给客户端)。
  5. void(通常与HttpServletResponse对象一起使用以之间写入响应体)。

2.1 参数变量

2.1.1 矩阵变量

URL中,允许在路径段中使用键值对参数。变量用;分割。例如:

/usr;name=黄先生;gender=男/city;address=深圳

注意: 默认情况下,Spring MVC并不支持从URL中解析矩阵变量。要启用对矩阵变量的支持,需要自定义RquestMappingHanddlerMapping的PathMatcher,或者重写WebMvcConfigurer接口的configurePathMatch方法,并设置一个自定义UrIPathHelper,调用其方法setRemoveSemicolonContent(false),来告诉Spring不要移除URL中的分号。

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
    UrlPathHelper urlPathHelper = new UrlPathHelper();
    urlPathHelper.setRemoveSemicolonContent(false);// 不要移除URL中的分号
    configurer.setUrlPathHelper(urlPathHelper);
}

可以通过@MatrixVariable 来提取这些变量,用MultiValueMap来装载所有的变量。

// /parameter/matrix1/admin;color=blue;name=jack
@GetMapping("/matrix1/{usr}") // 注意,这里一定要使用路径变量,路径变量后的矩阵变量才会生效
// 所以 /parameter/matrix1;color=red,yellow;size=big/admin;color=blue;name=jack 将匹配失败
public void matrix1(@PathVariable String usr,@MatrixVariable MultiValueMap<String,Object> map) {
    System.out.println("user:" + usr); // user:admin
    System.out.println("map:" + map); //map:{color=[blue], name=[jack]}
}

// parameter/matrix2/admin;subject=English,computer;age=19/sz;subject=math
@GetMapping("/matrix2/{usr}/{address}")
public void matrix2(@PathVariable String usr,@PathVariable String address,@MatrixVariable MultiValueMap<String,Object> map) {
    System.out.println("user:" + usr + ";address:" + address); // user:admin;address:sz
    System.out.println("map:" + map); // map:{subject=[English, computer, math], age=[19]}
}

// parameter/matrix3/admin;subject=English,computer;age=19/sz;subject=math
@GetMapping("/matrix3/{usr}/{address}")
public void matrix3(@PathVariable String usr,@PathVariable String address,@MatrixVariable(name = "subject", pathVar = "usr") List<String> subject1
,@MatrixVariable(name = "subject", pathVar = "address") String subject2) {
    System.out.println("subject1:" + subject1); // subject1:[English, computer]
    System.out.println("subject2:" + subject2); // subject2:math
}

2.1.2 会话变量

会话允许开发者在多个请求之间保持用户的状态信息。

HTTP协议本身是无状态的,服务器不会记住每个用户的请求之间的任何信息。然而,在实际开发者,我们经常需要跟踪用户的会话状态,如登录状态。

在Spring MVC中,会话通常由HttpServletSession对象来管理,这是Java Servlet API的一部分。其提供了存储和检索属性的方法,这些属性可以在用户的整个会话期间保持。

当用户第一次访问一个需要会话支持的Web应用时,Servlet容器在会话被创建时自动生成JESSIONID。如果要保持同一会话,则每次请求时,在cookie中需要携带这个会话ID。

@SessionAttributes

作用于类级别,将模型Model中的属性同步到HTTP会话中。默认请求下,模型中的数据存储到request域。该注解可以指定哪些模型属性存储到session中,使得这些属性可以在其他请求中共享。

@SessionAttribute

从会话中提取属性值

表Spring MVC中的会话属性注解

@RequestMapping("/session")
@RestController
@SessionAttributes("user")
public class SessionController {
    @GetMapping("/login")
    public void login(@RequestParam("username") String username, HttpServletRequest request, Model model) {
        System.out.println("login:" + request.getSession().getId());
        model.addAttribute("user",username);
        model.addAttribute("info","登录了");
    }

    @GetMapping("/info")
    public String info(@SessionAttribute("user") String user,HttpServletRequest request) { // 如果该sessionAttribute的值不存在,则请求会失败
        System.out.println("info:" + request.getSession().getId()); // 会话id需与上面的请求保持一致,才能获取到特定会话的值
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            System.out.println(cookie.getName() + ":" + cookie.getValue());
        }
        return user;
    }
}

2.1.3 @ModelAttribute

可以作用于有返回值的方法及处理器的参数,用于将属性值绑定到model的属性中,以用于在视图中使用。

作用于方法级别时,会在每次请求处理之前被调用,并且返回的模型属性会被添加到模型中,供后续的请求处理方法使用。

作用于处理器参数时,先从上面的方法返回值来填充模型,然后如果请求参数中包含了相应属性,则会用请求参数值进行覆盖。

@RequestMapping("/modelAttribute")
@RestController
public class ModelAttributeController {
    @ModelAttribute // 该方法会在每次请求处理之前被调用
    public User createUser() {
        return new User("刘女士","123456");
    }

    // /modelAttribute?username=黄先生
    @GetMapping
    public void get2(@ModelAttribute("user") User user) { // 如果传参的时候没有username及password属性,
        // 那么user的值为createUser的返回值,否则request的参数会覆盖其属性值
        System.out.println(user); // ModelAttributeController.User(username=黄先生, password=123456)
    }

    @Data
    public static class User {
        public String username;
        public String password;
        public User(String username, String password) {
            this.username = username;
            this.password = password;
        }
    }
}

2.1.4 @RequestParam 与 @RequestPart

@RequestPart 主要用于处理multipart/form-data类型的请求。注意,@RequestParam 同样也可以读取该类型的参数。区别在于,@RequestPart能读取参数类型为json的参数,并将其转化为对于的类型,而@RequestParam只能把它当作String类型处理。

@RequestMapping("/paramAndPart")
@RestController
public class ParamAndPartController {
    @PostMapping("/param")
    public void param(@RequestParam("jsonValue") String jsonValue,@RequestParam("file") MultipartFile file) throws IOException {
        System.out.println("jsonValue:" + jsonValue);
        System.out.println("file:" + file); // 获取的是文件路径或者文件名等文本信息,而不是文件本身的内容
        System.out.println(file.getInputStream()); // 到这里才涉及文件的操作,获取文件本身的内容
    }

    @PostMapping("/part")
    public void part(@RequestPart("jsonValue") User jsonValue, @RequestPart("file") MultipartFile file) throws IOException {
        System.out.println("jsonValue:" + jsonValue);
        System.out.println("file:" + file); // 会接收客户端发送的数据文件,并将其存储在服务端的临时文件中
        System.out.println(file.getInputStream());
    }

    @Data
    public static class User implements Serializable {
        String username;
        String password;
    }
}

图 使用postman时需要注意的地方

2.1.5 @RequestBody 与 HttpEntity

@RequestBody 的主要作用是自动将HTTP请求的请求体(body)中的数据绑定到方法参数上。这些数据通常以JSON或XML格式发送。使用该注解时,必须确保请求头中的Content-Type属性值正确地制定了请求体的格式。例如,对于JSON数据,Content-Type通常设置为application/json。@RequestBody 只能绑定一个请求体。

HttpEntity 用于表示一个HTTP请求和响应的实体。实例类有RequestEntity、ResponseEntity。如果将其用于参数的绑定请求体的格式要求与@RequestBody一样。

@RestController
@RequestMapping("/myRequestBody")
public class RequestBodyController {
    @PostMapping
    public void requestBody(@RequestBody User user) {
        System.out.println(user);
    }

    @PostMapping("/httpEntity")
    public void httpEntity(HttpEntity<User> userHttpEntity) {
        System.out.println(userHttpEntity.getBody());
    }

    @Data
    public static class User implements Serializable {
        private String username;
        private String password;
    }
}

2.2 响应

2.2.1 Flash Attributes

当我们进行重定向时,需要向后面的处理器传递参数,可以通过URL参数形式传递,但这样不安全。这时我们可以使用Flash Attributes。

其提供了一种在重定向期间保持状态信息的有效方式,同时避免了将状态信息存储在用户会话中的长期副作用。它一旦被读取,就会自动从session中删除。

@Controller
@RequestMapping("/flash")
public class FlashAttributeController {
    @GetMapping("/login")
    public String login(@RequestParam String username, RedirectAttributes redirectAttributes) {
        System.out.println("login username:" + username);
        redirectAttributes.addFlashAttribute("username",username);
        return "redirect:info";
    }

    @GetMapping("/info")
    public @ResponseBody String showInfo(@ModelAttribute("username") String username) {
        System.out.println("hello " + username);
        return username;
    }
}

2.2.2 @ReponseBody 与 ResponseEntity

@ResponseBody 主要用于将控制器方法返回的对象转换为特定的格式,并直接写入HTTP响应的body中。

ResponseEntity 表示整个HTTP响应,包括状态代码、响应体。其优先级高于@ResponseBody,即返回类型为ResponseEntity的情况下,即使方法同时标注了@ResponseBody,也不会对响应体产生影响。

2.2.3 JSON Views

Spring MVC 为Jackson的序列化视图提供了内置支持。序列化视图允许你在一个对象中选择性的包含某些字段,而不是所有字段。要与@ResponseBody或ResponseEntity一起使用。

@RestController
@RequestMapping("/jsonView")
public class JsonViewController {

    @GetMapping
    @JsonView(User.ShowAttribute.class)
    public User showUser() {
        User user = new User("黄先生", 28, "深圳");
        System.out.println(user); // JsonViewController.User(name=黄先生, age=28, address=深圳)
        return user; // 返回给前端 {"name": "黄先生", "address": "深圳"}
    }

    @Data
    public static class User {
        public interface ShowAttribute {}

        @JsonView(ShowAttribute.class)
        public String name;
        private Integer age;
        @JsonView(ShowAttribute.class)
        private String address;
        public User(String name, Integer age, String address) {
            this.name = name;
            this.age = age;
            this.address = address;
        }
    }
}

3 Controller的配置

3.1 @InitBinder

可以在控制器中指定多个@InitBinder方法,这些方法会在请求处理之前被调用,用于配置数据绑定。通过这些方法,可以定制WebDataBinder的行为。其作用范围为当前Controller下的所有请求(有数值绑定的)。

其具体作用是:1)自定义数据绑定;2)全局配置;3)处理特定类型的参数;4)处理空值或缺失值。

@RestController
@RequestMapping("/initBinder")
public class InitBinderController {
    @InitBinder
    public void initBinder1(WebDataBinder webDataBinder) {
        System.out.println("initBinder1");
    }

    @InitBinder
    public void initBinder2(WebDataBinder webDataBinder) {
        webDataBinder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
    }

    @GetMapping
    public void req1(@RequestParam String username) {
        System.out.println("req1:" + username);
    }
}

3.2 @ControllerAdvice

允许开发者定义全局的异常处理、数据绑定等逻辑,而不必在每个单独的controller中重复相同的代码。类似于AOP中的切面,但它专为web层设计。

@ControllerAdvice
public class CustomControllerAdvice {
    @InitBinder
    public void initBinder(WebDataBinder binder) { // 在请求中,方法参数中每有一个参数执行绑定,则该方法都会执行
        System.out.println("CustomControllerAdvice 全局数据绑定处理"); // 可用来配置转换器
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> exceptionHandler(Exception e) { // 全局异常处理
        return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ModelAttribute("webName")
    public String webName() {
        System.out.println("全局@ModelAttribute");
        return "Hello Web";
    }
}
举报

相关推荐

0 条评论