SpringMVC和Servlet技术栈等同,都用于controller层/web层开发。但是SpringMVC的开发难度比Servlet更简单。
创建SpringMVC控制类,将这个类变成bean。 然后在这个类中写表现层的东西。使用@RequestMapping定义请求资源路径,使用@ResponseBody定义响应体。
新建一个springmvc的配置文件类加载Controller的bean。这里注意springmvc的配置文件和spring的配置文件要区分开,springmvc只导入表现层的bean,spring载入dao层和service层的bean。
其实这些东西都是很固定的,要用的时候只需要复制粘贴就可以了,只有一些是需要根据具体的需求进行修改的。
spring的IoC容器不要加载springmvc的bean,springmvc的IoC容器不要加载spring的bean。要实现这种功能,有两种方案:
注解@RequestMapping(“/brand”)是请求映射路径,用于设置当前控制器方法的资源请求路径。这个注解既可以用在controller类上,也可以用在方法上。类上的可以不用但是方法上的一定要用。controller中一个方法就是一个资源。
注意,不同的controller中资源的路径不要相同,如果有两个资源路径相同,当一个请求进来后,springmvc不知道要将该请求映射到哪个资源。事实上,如果写了两个相同路径的资源,springmvc在编译的时候就会报错了。
(1)接收少量请求参数
直接在方法的参数上设置形参,这个形参就代表着请求的参数。
形参需要跟请求参数匹配:
@RequestParam("请求参数键名")
来建立请求参数与形参之间的关系。(2)接收大量请求参数
第一种:方法形参写一个pojo实体类(实体类的属性名字必须和请求参数的名字完全一致)。框架会自动将请求参数装配到实体的属性中,非常方便。(如果pojo中的属性有引用数据类型,请求参数需要写成引用对象名.引用对象属性的形式。)
第二种:方法参数写一个数组。请求参数的形式需要写成:
键相同,值不同。如此一来,所有的值会自动转换为一个数组。
第三种:方法参数写一个集合。形参前面需要写一个注解@RequestParam,这样一来,所有请求键值对会被视为一个大的请求参数,当方法参数又是集合类型时,springmvc会把键值对中的值装入集合中。如果不写注解,集合会被识别成pojo实体类,请求参数会被设置成集合的属性(报错,集合中不会有这些属性),而不是集合的数据。请求参数的写法跟上边的数组一样,键值对的所有键名需要一致。
首先先导入一个json依赖,用于实体类、数组类、集合类与json数据的相互转换。
由于springmvc不知道请求携带的是json数据,所以还要告诉springmvc要进行json数据转换。在springmvc的配置类上使用注解@EnableWebMvc
。
json数据在请求体中。所以,接收请求数据时,方法的形参前需要写个注解@RequestBody
。(要注意,实体类不要写构造方法,默认无参就可以了。getter和setter写上。)
springmvc之所以能将请求参数/请求体中的数据转成Java中的对象,其实是使用了各种内置的springmvc转换器对象。
当 Spring MVC 框架内置的类型转换器不能满足需求时,开发者可以开发自己的类型转换器类。
自定义类型转换器类需要实现 Converter 接口,重写 convert(S) 接口方法。之后,需要将该类注册成bean。
也可以采取另一种方式,使用匿名内部类来实现接口。详见下边的接收时间日期的第二种方式,就使用了这种方式。
如此一来,当在自动转换时,springmvc就多出了一个自动转换的选择。我们的自动转换类也能生效啦。
实际上jdk8以前的日期时间处理都是用Date和Calendar类。不过这两玩意实在是太难用了,一直被喷,所以jdk8出现了新的日期时间处理LocalDate/LocalDateTime。
第一种,将请求参数转成Date类。
在方法参数中使用Date类型来作为参数类型。当请求参数中携带日期格式的值时,会自动转为Date对象。值得注意的是,请求参数中日期的格式应该为yyyy/MM/dd
,如果要用其他格式,需要为方法参数加上注解@DateTimeFormat
第二种,将请求参数转成LocalDate/LocalDateTime。springmvc的内置转换类是没有这种转换的,所以需要自定义转换器。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.converter.HttpMessageConverter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Configuration
public class DateConfig {
@Bean
public Converter<String, LocalDate> localDateConverter() {
//匿名内部类,实现了Convertor接口
return new Converter<>() {
@Override
public LocalDate convert(String source) {
return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
};
}
@Bean
public Converter<String, LocalDateTime> localDateTimeConverter() {
//匿名内部类,实现了Convertor接口
return new Converter<>() {
@Override
public LocalDateTime convert(String source) {
return LocalDateTime.parse(source, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
};
}
}
接收请求数据写在形参中,那么响应数据写在方法的返回值中。只需要将对象返回,之前导入的坐标会自动将对象转化为json数据响应。
主要是@ResponseBody在发挥作用。如果不写这个注解返回的响应就是文本了。
@ResponseBody会将返回的响应自动转化为json数据。
springmvc能够自动将return的数据转为json数据,是通过一个接口(不是Convertor接口)完成的。
传统风格描述资源格式:
http://localhost/user/selectById?id=1
REST风格:
http://localhost/user/1
REST风格的优点是隐藏。既然做了隐藏,又该如何知道请求想要表达的是增、删、改、查中的哪种操作呢?
其实总结起来就是:请求资源限定为四种格式:GET、POST、PUT、DELETE。
客户端发出请求的时候,需要标明是哪一种格式。到服务器的表现层,会自动匹配到对应格式的资源。
这里就有两步:
客户端发出请求给特定控制器(控制器的路径是要写上的),标明是哪一种格式(目前是直接在postman里直接设置进行测试)
服务端对应的控制器接收请求,在控制器中匹配是哪一种格式,找到对应方法。
==这里,匹配是哪一种格式是由请求映射路径的注解@RequestMapping来进行的。现在@RequestMapping就不能只是简单地写资源路径,还要写上REST风格的行为动作(注解中加上一个参数method=),以便和请求匹配。==请求如果有路径变量,请求路径映射注解中要写上路径变量的占位符,而且方法形参中要写上@PathVariable注解。
把@ResponseBody和@RequestMapping(”控制器路径“)从各个方法上提出来写到类名上,把@ResponseBody和@Controller合二为一叫做@RestController。把REST风格的行为动作在各个方法上写成@PostMapping()、@DeleteMapping(”/{路径变量名}“)等等。这样就大幅简化了开发。
注意:对于Post和Put这些没有路径变量的行为动作,请求体中是可以有json数据的。同样,直接在方法形参上注解@RequestBody就可以了。
各个层级都会出现异常,那么异常代码书写在哪一层?所有的异常均抛出到表现controller层进行处理。
如果每有一个异常就写代码进行处理,太过臃肿,使用aop思想处理表现层中的异常。
spring中定义了一个异常处理器统一集中地处理表现层中异常。在controller包中定义一个类:ProjectExceptionAdvice类,注解是@RestControllerAdvice然后在这个类中写方法拦截异常。
注意:这个类要被springmvc的配置类加载到。然后要在这个类的方法中写一个注解:@ExceptonHandler,里边写上表现层抛出的异常类型,根据异常类型到这个方法中执行。
在访问控制器资源的时候,首先进入拦截器方法进行处理,访问完控制器资源之后,也要进入拦截器方法进行一定处理。
制作一个拦截器类,放在controller包下。而且要把这个类变成一个bean然后被springmvc加载。
实现拦截器接口,重写三个方法。第一个方法preHandle()在访问控制器资源前执行,第二个方法postHandle()在访问控制器资源后执行,第三个方法afterCompletion()在第二个方法执行后执行。
==拦截器三个方法的参数就是拦截到的请求以及响应。==图片中没有写出来。
如果三个方法中,第一个方法return true,就能继续访问controller资源,然后进入第二个方法和第三个方法,return false,不能访问controller资源。return true就是放行,return false就是拒绝放行。
定义一个配置类,继承WebMvcConfigurationSupport
,实现addInterceptor
方法,在方法中添加上边写的拦截器并设定拦截路径。拦截的路径可以写多个,并且可以用*通配符。比如/books/ *
表示拦截访问books下所有资源的请求。
注意使用@Configuration将这个类加载到SpringMVC的容器中。
除了以上方式外,有一种侵入式比较强但是比较方便的方法。同样要写一个拦截器类,然后让springmvc配置类实现WebMvcConfigurer
接口,然后重写方法添加拦截器并设置拦截路径。
如果有多个拦截器,可以设置这些拦截器的执行顺序。这就形成了一个拦截器链。
如果创建了拦截器1、拦截器2、拦截器3,这三个拦截器按照拦截器2、拦截器1、拦截器3的顺序进行注册。
当一个请求进来,它的经过顺序为:
如果请求在1pre被拒绝放行,它会跳到1after后即2after继续执行。
一个递归的操作。