SpringMVC给出了拦截器来实现单元方法的拦截,拦截器的执行是在DispatcherServlet之后和单元方法之前的。
注意:只有URL匹配到了控制单元,拦截器才能生效。
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor.preHandle");
return fasle;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor.postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor.afterCompletion");
}
}
拦截器是由DispatcherServlet接收到请求后来调用的,所有需要将拦截器配置为bean,并且配置其拦截范围。
作用:
执行拦截的方法preHandle(),返回false表示拦截此次请求,返回true表示放行。
执行时机:
单元方法执行之前。
参数:
HttpServletRequest request:此次拦截的请求的request对象。
HttpServletResponse response:此次拦截的请求的response对象。
Object handler:HandlerMethod类型,存储了拦截的单元方法的method对象。
返回值:
boolean类型,false表示拦截,true表示放行。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod hm=(HandlerMethod)handler;//强转为HandlerMethod类型
Method method = hm.getMethod();//获取此次请求的单元方法的方法对象
System.out.println(method.getName());
System.out.println("MyInterceptor.preHandle");
return false;
}
作用:
执行拦截的方法postHandle(),可以对ModelAndView进行操作,Model数据进行校验,View操作等。
执行时机:
单元方法执行之后,视图解析器解析渲染视图之前。
参数:
HttpServletRequest request:此次拦截的请求的request对象。
HttpServletResponse response:此次拦截的请求的response对象。
Object handler:HandlerMethod类型,存储了拦截的单元方法的method对象。
ModelAndView:存储了model和view信息的对象。
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//重置跳转的资源路径
String viewName = modelAndView.getViewName();
//modelAndView.setViewName("forward:/bb.jsp");
//获取Model对象中存储的流转数据
Map model = modelAndView.getModel();
System.out.println("MyInterceptor.postHandle----"+viewName+"-----"+model.get("str"));
}
作用:
执行拦截的方法afterCompletion(),无论是否出现异常都会执行,可以处理异常,清理资源等。
执行时机:
视图解析器解析渲染视图完成之后。
参数:
HttpServletRequest request:此次拦截的请求的request对象。
HttpServletResponse response:此次拦截的请求的response对象。
Object handler:HandlerMethod类型,存储了拦截的单元方法的method对象。
Exception:存储异常信息的对象,如果没有异常信息则默认为null。
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor.afterCompletion");
}
自定义拦截器,拦截器SpringMVC提供的,只拦截控制单元。
拦截控制单元:
1.执行控制单元之前 执行拦截器preHandle方法。对请求进行相关的处理。
2.执行控制单元之后 执行拦截器postHandle方法。对应modelandview进行相关操作。
3.向客户端完成响应 执行拦截器afterCompletion方法。进行相关结尾的操作,无论是否出现异常都会执行。
public class MyInterceptor01 implements HandlerInterceptor {
// 执行控制单元之前 执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle 执行了 111");
//false,不放行 true,放行
return true;
}
// 控制单元执行完成后 执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle 执行了 111");
}
/*
* 没有使用@ResponseBody注解:渲染完页面后执行
* 使用了@ResponseBody注解:不再需要渲染页面。postHandle 执行后 执行 afterCompletion
*
* 页面中内容响应完成后执行 afterCompletion
* */
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion 执行了 111");
}
}
拦截器类创建好后需要在springmvc.xml文件中配置拦截器的bean以及其拦截范围。
拦截器栈指多个拦截器。当一个控制单元被多个拦截器拦截时,就形成了拦截器栈。拦截器栈中拦截器有着严格的执行顺序。执行顺序按照配置顺序执行。先配置的优先级更高。
例如:拦截器A和拦截器B,优先级更高指以控制单元(demo)为核心,前面的方法优先级高的先执行,后面的方法优先级高的后执行。
preHandle1 -> preHandle2 -> demo -> postHandle2 -> postHandle1 -> afterCompletion2 -> afterCompletion1。
配置拦截器栈分为局部配置和全局配置。全
全局配置:
局部配置:
因为在中只能有一个,所有如果希望使用局部方式配置拦截器栈,需要配置多个标签。
来源不同
拦截器是SpringMVC中的技术,过滤器是Java EE中的技术。
生效位置不同
拦截器是进入DispatcherServlet后才能执行,过滤器是进入到Servlet容器后就可以触发。
目标不同
拦截器拦截的目标是HandlerMethod(控制单元,控制器方法),过滤器可以过滤所有的URL。
运行机制不同
拦截器是在HandlerMethod执行前后和视图处理完成后执行,分为三部分。过滤器只能在目标资源前后执行。
接口中方法类型不同
拦截器中的方法都是default方法,可以重写也可以不重写。过滤器中的方法都是abstract方法,如果当前类不是抽象类,必须重写。
上下文不同
拦截器被Spring MVC管理,可以获取到Spring容器中内容。Filter被Tomcat管理,所以无法获取Spring容器内容。
1.过滤器(Filter):由JavaEE提供的过滤器. 请求到达资源(servlet,页面,css,js,img,...)之前都要经过过滤器,资源响应回到客户端之前经过过滤器。
2.拦截器(Interceptor):由SpringMVC提供的拦截器. 请求到到控制单元之前经过拦截器(preHandle),控制单元执行完成后经过拦截器(postHandle),向页面响应完成,经过拦截器(afterCompletion)。
项目和项目之前访问的网址或ip:port称为域(domain)。
跨域:当前项目的协议、ip、端口和访问的URL的协议、IP、端口中有一个不同,这种访问就叫跨域。
例如:当前项目http://localhost:8080 访问了 http://127.0.0.1:8080 就是跨域访问。
跨域只发生在Ajax请求中。
Ajax研发之初为了保证安全性,设置默认情况下不允许跨域访问。
浏览器为了保证Ajax默认不允许跨域实现,浏览器中都有一个同源策略。
同源策略:当使用ajax请求时,不允许跨域访问。只能访问当前域的资源。
解决跨域的方法有很多,常见的解决方案:
jsonp : Spring 4的支持。
设置响应头:Spring 5支持。只需要在允许被跨域访问的方法上面添加@CrossOrigin注解即可。
只要在控制单元方法上添加了@CrossOrigin注解后,会在响应头中添加Access-Control-Allow-Origin:*。
Access-Control-Allow-Origin是HTTP协议中允许哪些IP的项目跨域访问,*表示所有IP。
等效于在响应头中直接添加允许跨域参数:
@RequestMapping("/demo")
@ResponseBody
public String demo(HttpServletResponse response){
response.setHeader("Access-Control-Allow-Origin","*");
return "hello";
}
为了简单直接使用@CrossOrigin注解:
@RequestMapping("/demo")
@ResponseBody
@CrossOrigin
public String demo(){
return "hello";
}
在Spring MVC支持异常处理。不会呈现给用户500界面。而是当出现异常时交给某个特定的控制器。
如果是一个上线的项目,呈现了500界面,会非常影响用户体验度。同时也显示公司实力有问题、不专业。
配置在@Controller的控制器类中,只有当前这个控制器类的控制单元出现异常时才能执行,其他类的控制单元出现异常不能执行。
每个控制器类中可以有多个处理异常的方法。每个方法上面只需要有@ExceptionHandler,千万别添加了@RequestMapping注解。
@Controller
public class DemoController {
@RequestMapping("/demo")
@ResponseBody
public String demo2(){
Object obj = null;
obj.toString();
return "demo2";
}
@ExceptionHandler(value = ArithmeticException.class)
public String myexception(){
System.out.println("Demo-1");
return "forward:/exception.jsp";
}
@ExceptionHandler(value = Exception.class)
public String myexception2(){
System.out.println("Demo-2");
return "forward:/exception2.jsp";
}
}
因为@ControllerAdvice已经继承了@Component注解,所以类上只添加这个注解就可以了。
不需要在添加@Controller注解了。
小提示:
如果配置了局部异常处理器和全局异常处理器,优先匹配局部异常处理器。
@ControllerAdvice
public class MyExceptionController {
@ExceptionHandler(value = ArithmeticException.class)
public String myexception(){
System.out.println("MyException-1");
return "forward:/exception.jsp";
}
@ExceptionHandler(value = Exception.class)
public String myexception2(){
System.out.println("MyException-2");
return "forward:/exception2.jsp";
}
}
在Spring MVC中包含HandlerExceptionResolver组件,专门负责处理异常的。接口中只包含一个resolveException方法。
/error1.jsp
/error2.jsp
- 数据校验分为客户端数据校验和服务端数据校验。常用在登录、注册功能中。
- 数据校验是软件开发过程中必不可少的一个环节。一个项目只有客户端数据校验是不严谨的,为了防止不法人员直接通过URL或HTTP工具非法访问服务端,发送非法数据,保险起见,服务端也应该具有数据校验,这种客户端和服务端都有数据校验时,称为双重校验。
- 客户端数据校验直接使用客户端脚本校验即可。例如在B/S模型项目中,可以选择使用JavaScript或jQuery进行客户端数据校验。
- 服务端数据校验根据使用的技术完成校验。如果是Servlet技术可以自己编写校验逻辑,所有的校验过程都是通过正则表达式、等值判断等进行校验。而在Spring MVC中数据校验包含两种:(1)基于注解方式 。 (2)自定义校验类的方式。
JCP(Java Community Process)是一个开发的国际组织,里面包含了一些Java开发者和其他被允许加入的成员。JCP组织主要负责对Java社区进行发展和更新。维护的规范包含:J2ME、J2SE、J2EE、XML等相关规范。组织成员可以提交JSR(Java Specification Requests,Java 规范提案),待组织成员通过提案后会把相关内容加入到下一个版本的规范中。
JSR 303是Java EE 6规范的子规范。叫做Bean Validation。这些规范都是注解。
在Java开发中使用的最多的JSR 303具体实现就是Hibernate框架中Hibernate-Validator。它对JSR 303的所有约定(constraint)都做了实现,同时还进行了一定的扩充。
Hibernate Validator对JSR实现都存在于Validation-api.jar的javax.validation.constraints包中。
里面所有注解都包含message属性,表示校验不通过后日志打印的信息。
Hibernate Validator对JSR 303具体实现的解释:
注解 | 含义 |
---|---|
@AssertFalse | 类型必须是布尔,取值必须为false |
@AssertTrue | 类型必须是布尔,取值必须为true |
@DecimalMax("3") | 最大值为3,value属性是String类型 |
@DecimalMin("1") | 最小值为1,value属性是String类型 |
@Digits(integer = 10,fraction = 3) | integer:整数位最大长度,fraction小数位最大长度 |
必须是邮箱地址。只要中间包含@,且@前后都具有超过1位的字符就能通过校验。字符可以是数字字母下划线 | |
@Future | 类型必须是时间类型,允许为null,如果设置值必须是一个将来的时间 |
@FutureOrPresent | 类型必须是时间类型,允许为null,如果设置值必须是一个将来或现在的时间(精确到秒) |
@Max(5) | 最大值为5,value属性是long类型 |
@Min(1) | 最小值为1,value属性是long类型 |
@Negative | 必须是负数,对数据类型没有要求 |
@NegativeOrZero | 必须是负数或零,对数据类型没有要求 |
@NotBlank | 用在String类型。不能是空白(null和"") |
@NotEmpty | 用在String类型。不能是空白(null和"") |
@NotNull | 不能为null,可以是""。可以用在所有类型中。对于八大基本数据类型来说,永远不为null |
@Null | 必须为Null。可以用在所有类型中。对于八大基本数据类型来说,永远不可能为null |
@Past | 类型必须是时间类型,必须是一个过去的时间。精确到秒 |
@PastOrPresent | 类型必须是时间类型,必须是一个过去的时间或现在的时间。精确到秒 |
@Pattern(regexp = "\w{1,6}") | 必须满足正则表达式。regexp是必有属性 |
@Positive | 必须是正数,对数据类型没有要求 |
@PositiveOrZero | 必须是正数或零,对数据类型没有要求 |
@Size(min = 1,max = 10) | 用在String类型。个数必须在1和10之间 |
Hibernate-Validator额外补充,这些注解都在Hibernate-validator.jar的org.hibernate.validator.constraints包中。
注解 | 含义 |
---|---|
@Length(min = 1,max = 10) | 用在String类型。长度需要在1和10之间 |
@Range(min = 1,max = 10) | 数据类型没有要求。取值范围需要在1和10之间 |
@URL(port = 8080,host = "127.0.0.1",protocol = "https") | 需要是一个合法的URL。默认情况下只要是以http:开头即可。可以通过port限制端口、host限制主机名、protocol限制协议 |
除了项目正常的依赖以外,额外需要导入Hibernate-validator依赖。
注意:对于Tomcat8插件来说,最高支持到6.1.x版本,对于更高的6.2.x或7.x版本是不支持的。导入后启动会报异常。如果希望使用更高版本的依赖,可以使用更高版本Tomcat(本机Tomcat)。
对于Tomcat8插件正确的依赖导入示范:
如果导入过其他版本的Hibernate-Validator,一定要先停止Tomcat8插件,再clean一下。否则即使导入的是下面版本,也会报上面的异常。
org.hibernate.validator
hibernate-validator
6.1.7.Final
示例中只是以NotNull和Length进行举例。
(1)@NotNull中message是可选属性,如果设置了message,在违反规则后日志会打印message中内容。
(2)@Length是设置字符串长度。
所以name必须不能是null的,且长度是2-6位。
注解放在实体类的属性上面,每个实体类属性都支持配置多个注解,这些注解同时生效。
public class People {
@NotNull(message = "姓名不能是null")
@Length(min = 2,max = 6,message = "长度应该是2-6位")
private String name;
private String age;
// 省略Getter和Setter
}
控制单元中必须添加@Valid注解,否则校验不生效。
@RequestMapping("/valid")
public String testValid(@Valid People peo){
System.out.println(peo);
return "/abc.jsp";
}
如果配置了日志文件,需要将日志级别设置为WARN。
组件是将处理某类问题的代码进行封装的整体模块。一个大的问题可以拆分为不同的小问题,解决每个小问题的代码封装可以称之为组件,但是组件又是无法独立运行的,必须结合其他组件一起才能最终解决问题。
SpringMVC将Servlet访问的流程整体封装了起来,浏览器发起请求后调用单元方法来处理请求,这个整个过程从接收到请求,到找到单元方法并调用执行,再到响应结果给浏览器都是由不同的组件协同来实现的,有如下组件:
不同的组件在整个运行过程中的具体表现其实就是不同的对象,也就是不同对象之间的相互调用完成了请求的处理,组件的初始化应该在请求来之前就完成的,当服务器启动的时候会先完成对DispactherServlet的初始化创建,在DispatcherServlet被初始化的时候其底层内部也会完成第2到第10个组件的初始化,调用其initStrategies方法来完成。
protected void initStrategies(ApplicationContext context) {
this.initMultipartResolver(context);
this.initLocaleResolver(context);
this.initThemeResolver(context);
this.initHandlerMappings(context);
this.initHandlerAdapters(context);
this.initHandlerExceptionResolvers(context);
this.initRequestToViewNameTranslator(context);
this.initViewResolvers(context);
this.initFlashMapManager(context);
}
DispatcherServlet:前端控制器。Spring MVC的入口,也是整个流程控制中心。其他组件由DispatcherServlet统一调度,降低了组件和组件之间的耦合度。
MultipartResovler:多部分处理器。文件上传时需要使用。
LocaleResolver:解决客户端的区域和时区问题。
ThemeResolver:主题解析器。提供自定义布局。
HandlerMapping: 映射处理器。主要负责处理URL,并找到对应的HandlerMethod。简单说就是找@RequestMapping注解中映射路径是否有和URL匹配的。
HandlerAdapter:适配器。负责调用具体的HandlerMethod。
HandlerExceptionResovler:异常处理器。异常处理,根据不同异常返回视图。
RequestToViewNameTranslator:从请求中获取到视图名称。
ViewResovler:视图解析器,负责解析字符串视图名和物理视图文件的。
FlashMapManager:主要用于存储属性的,本质是一个Map。多用在重定向时。FlashMap在重定向之前存储,重定向之后删除。
ModelAndView:模型和视图。Spring MVC中提供的模型和视图接口。
HandlerInterceptor:拦截器。拦截控制器资源的。
客户端向服务端发起请求,Spring MVC总体入口中央调度器DispatcherServlet进行请求分发。
中央调度器DispatcherServlet把URL交给映射处理器HandlerMapping进行解析URL。
映射处理器HandlerMapping将请求映射为HandlerExecutionChain处理器执行链
可以为多个处理器拦截器HandlerInterceptor
处理器Handler对象(处理Controller)。
将处理器执行链HandlerExecutionChain返回到中央调度器DispatcherServlet。
DispatcherServlet根据返回的处理器执行链HandlerExecutionChain获得的处理器Handler,根据处理器Handler选择处理器适配器HandlerAdapter。
执行拦截器的preHandle()方法。调用具体的Handler处理器(处理Controller),在填充Handler的入参过程中会执行数据转换、数据格式化、数据验证,调用具体的Controller完成处理功能,并创建ModelAndView对象。
执行拦截器的postHandle()方法。
将ModelAndView对象返回到处理器适配器HandlerAdapter。
处理器适配器HandlerAdapter将ModelAndView对象返回到中央调度器DispatcherServlet。
中央调度器DispatcherServlet调用视图解析器ViewResolver解析视图。
将解析的视图View对象返回到中央调度器DispatcherServlet。
渲染视图,将视图返回到中央调度器DispatcherServlet,执行拦截器afterCompletion()方法。
中央调度器DispatcherServlet相应回到浏览器。