Spring Web MVC -- 视图层
官网:https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html
术语
JSRs: Java Specification Requests,Java 规范提案
JSR 303:JSR第303号提案,即Bean Validation规范
Hibernate Validator:JSR 303的参考实现,和 Hibernate ORM 没有关系
Spring Web Flux:与Spring Web MVC对等,采用函数式(异步非阻塞)编程,能提高吞吐量,运行在Reactive容器,最终实现与Spring MVC一样的web服务
DispatcherServlet:Spring MVC提供的Servlet,不处理具体业务,负责调度
Controller:被DispatcherServlet调度,处理具体业务
IDEA 配置 Spring MVC
1、配置Maven依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId> <!-- 包含ContextLoaderListener和DispatcherServlet -->
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId> <!-- servlet支持 -->
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <!-- 用于对象转json -->
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId> <!-- 用于校验 -->
<version>6.0.12.Final</version>
</dependency>
2、给IDE配置 Tomcat服务器:Settings -> Application Servers -> 添加 Tomcat Server(即servlet-api.jar 和 jsp-api.jar)
3、给项目配置tomcat:右上角 -> Edit Configurations -> 添加 Tomcat Server -> 添加Deployment
-- 点击绿色三角,就可以自动部署和运行了
-- debug模式运行,就可以打断点了
-- 可以查看tomcat日志了
-- 可以在Server -> Output窗口查看System.out的输出了
Spring MVC 应用组成
1、Servlet容器,项目应用配置 web.xml
<web-app>
<context-param> <!-- 指定Spring 配置文件 -->
<param-name>contextConfigLocation </param-name>
<param-value>classpath:applicationContext.xml</param-value> <!-- classpath是指resources目录 -->
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Spring MVC封装的Servlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param> <!-- 指定Spring MVC配置文件,默认为./dispatcher-servlet.xml -->
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<!-- Servlet要处理的url路径 -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
2、Spring配置文件 applicationContext.xml
3、DispatcherServlet 默认读取的配置文件 /WEB-INFO/dispatcher-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<mvc:annotation-driven /> <!-- 使用注解驱动,来扫描@Controller -->
<context:component-scan base-package="com.hogen.controller"/> <!-- 要扫描的包 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/> <!-- 视图方案,指定寻找jsp的路径和后缀 -->
<mvc:interceptors> <!-- 拦截器配置 -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.hogen.interceptor.RoleInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
4、控制器 Controller
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller ("myController") // 注解本类为Controller
@RequestMapping ("/my") // 注解 /my 下的请求都由本类处理
public class MyController {
@Autowired
private CityService cityService; // 注入Mybatis的Service
@RequestMapping ("/index") // 注解本方法处理 /my/index 请求
public ModelAndView index (HttpSession session, HttpRequest request, HttpCookie cookie) { // 可传入这些Servlet原生参数
ModelAndView mv = new ModelAndView();
mv.setViewName("dir/index"); // 指定 jsp 路径
mv.addObject ("role", role) ; // 添加数据到session,给jsp用
return mv ; // 返回一个jsp页面
}
@RequestMapping(value="/index2", method=RequestMethod.POST) // 指定请求方法
public ModelAndView index (@RequestParam("id") Long id, @SessionAttribute("userName") String userName) {
// 获取url参数 和 session中的数据,@RequestParam("id") Long id 可以简写为@RequestParam Long id
}
@RequestMapping(value="/index/{id}")
public ModelAndView index (@PathVariable("id") Long id, @RequestParam("file") MultipartFile file, , @RequestParam("files") MultipartFile[] files) {
// 从url路径中获取参数;上传单个文件;上传多个文件
}
@RequestMapping(value="/index3")
public ModelAndView index (@RequestBody RoleParams roleParams) { // 从请求提中获取数据
// 得先定义一个RoleParams类,拥有的成员就是这个请求有的参数
}
@RequestMapping ("deleteRoles"}
public ModelAndView deleteRoles(@RequestBody List<Long>idList) { // 接收一个列表
}
@RequestMapping ("index5"}
public ModelAndView index5(String param1, String param2) { // 序列化后的参数,即表单数据
ModelAndView mv = new ModelAndView();
mv.addObject ("role","ddddd");
mv.setView(new MappingJackson2JsonView()); // 返回json
return mv;
}
@RequestMapping ("index6"}
public ModelAndView index6(ModelAndView mv) {
mv.addObject ("id", 1111); // 传递参数
mv.setViewName ("redirect: ./index5"); // 服务器端重定向
}
@RequestMapping ("index7"}
public ModelAndView index7(RedirectAttributes ra) {
ra.addFlashAttribute("role", role) ; // 用对象来传递参数,临时保存到session, 跳转后清除
mv.setViewName ("redirect: ./index5"); // 服务器端重定向
}
@RequestMapping ("index8"}
public ModelAndView index8(@RequestHeader("User-Agent") String userAgent,
@CookieValue("JSESSIONID") String sessionId) {
// 从Cookie 和 请求头中获取信息
}
}
5、拦截器 Interceptor
public class RoleInterceptor extends HandlerInterceptorAdapter {
// 生命周期 preHandle -> servlet处理 -> postHandle -> 处理jsp -> afterCompletion
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true; // 返回 false 则直接结束
}
// 多个拦截器时,preHandle按配置顺序执行,postHandle逆序执行,afterCompletion逆序执行
public void postHandle(HttpServletRequest request, HttpServletResponse response , Object handler ,ModelAndView modelAndView) throws Exception {
}
// 多个拦截器时,一个preHandle返回false,则除了preHandle已经返回true的拦截器会执行它的afterCompletion,其他都不执行
public void afterCompletion(HttpServletRequest request,HttpServletResponse response ,Object handler , Exception ex ) throws Exception {
}
}
校验
1、用来包装请求体的 POJO
public class City {
@NotNull(groups={First.class}) // 不能为空;分组,First为一个interface
@Min(1) // 最小值
@Max(100) // 最大值
private Long id;
@DecimalMin(value="1.1") // 最小值
@DecimalMax("500000.00") // 最大值
private Double price ;
@Size(min=0,max=256) // 长度范围
@Pattern(regexp="^[a-z]+$", message="格式不对") // 正则匹配,给出错误信息
private String cityName;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") // 指导json数据转换
@DateTimeFormat(pattern="yyyy-MM-dd") // 日期格式
@Future // 得是将来的日期
private Date date;
}
2、业务校验类
POJO中的注解实现了格式校验,这里实现业务校验
public class CityValidator implements Validator {
@Override
public boolean supports(Class<?> aClass) {
return City.class.equals(aClass);
}
@Override
public void validate(Object target, Errors errors) {
City city = (City) target;
errors.rejectValue("cityName", null ,"业务校验错误"); // 这里设置的错误字段得是POJO中有的字段
}
}
3、Controller
@Controller
@RequestMapping ("/my")
public class MyController {
@InitBinder // 引入业务校验类
public void initBinder(DataBinder binder ) {
binder.setValidator(new CityValidator());
}
@RequestMapping(value="/index")
public ModelAndView index1(@Valid City city, Errors errors){ // @Valid 标明需要校验,不支持分组;@Validated({First.class})支持分组
ModelAndView mv = new ModelAndView();
if (errors.hasErrors()) { // 有校验错误
List<FieldError> errorList = errors.getFieldErrors() ;
for (FieldError error : errorList){
mv.addObject (error.getField(), error.getDefaultMessage()); // 出错字段 和 错误信息
}
}
else{
mv.addObject ("success","000");
}
mv.setView(new MappingJackson2JsonView());
return mv;
}
}
最佳实践
1、会话确认拦截器
preHandle()中判断session中的登录信息,未登录就重定向到登录页面,并返回false