MVC 分层
**MVC:**Model View Controller(模型-视图-控制器)
-
**模型(Model):**处理数据逻辑的部分;在web应用中,他通常包含与数据库交互的代码,负责数据的存储、检索和更新
-
**视图(View):**将数据渲染为用户界面,视图只展示页面,不包含业务逻辑
-
**控制器(Controller):**模型和视图之间的协调者,它接收用户的输入,调用视图和模型完成用户的请求
核心组件
-
**前端控制器(DisPatcherServlet):**负责接收请求、分发,给予客户端响应
-
处理器映射器(HandlerMapping): 根据URL匹配查找能处理的
Handler
,并会将请求涉及到的拦截器和Handler
一起封装 -
处理器适配器(HandlerAdapter): 根据
HandlerMapping
找到的Handler
,适配执行对应的Handler
-
**请求处理器(Handler):**处理实际请求的处理器(可理解为方法)
-
视图解析器(ViewResolver): 根据
Handler
返回的逻辑视图,解析并渲染成实际视图,并传递给DisPatcherServlet
响应给客户端
DisPatcherServlet
SpringMVC围绕前端控制器模式设计,DisPatcherServlet需要Java配置或者XML配置根据Servlet规范进行声明和映射**(一般使用XML配置)**
-
Java配置注册并初始化DisPatcherServlet
|---|------------------------------------------------------------------------------------------------|
| |public class MyWebApplicationInitializer implements WebApplicationInitializer {
|
| | |
| |@Override
|
| |public void onStartup(ServletContext servletContext) {
|
| | |
| |// Load Spring web application configuration
|
| |AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
|
| |context.register(AppConfig.class);
|
| | |
| |// Create and register the DispatcherServlet
|
| |DispatcherServlet servlet = new DispatcherServlet(context);
|
| |ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
|
| |registration.setLoadOnStartup(1);
|
| |registration.addMapping("/app/*");
|
| |}
|
| |}
| -
web.xml配置注册并初始化DisPatcherServlet
|---|------------------------------------------------------------------------------------------|
| |<web-app>
|
| | |
| |<listener>
|
| |<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
| |</listener>
|
| | |
| |<context-param>
|
| |<param-name>contextConfigLocation</param-name>
|
| |<param-value>/WEB-INF/app-context.xml</param-value>
|
| |</context-param>
|
| | |
| |<servlet>
|
| |<servlet-name>app</servlet-name>
|
| |<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
| |<init-param>
|
| |<param-name>contextConfigLocation</param-name>
|
| |<param-value></param-value>
|
| |</init-param>
|
| |<load-on-startup>1</load-on-startup>
|
| |</servlet>
|
| | |
| |<servlet-mapping>
|
| |<servlet-name>app</servlet-name>
|
| |<url-pattern>/app/*</url-pattern>
|
| |</servlet-mapping>
|
| |</web-app>
|
运行原理
客户端发送请求到前端控制器DisPatcherServlet
,前端控制器调用处理器映射器HandlerMapping
查找请求对应的处理方法Handler
,并将涉及到的拦截器和Handler
一起封装返回给前端控制器,前端控制器再调用处理器适配器HandlerAdapter
执行Handler
,并将数据放入Model
中或直接返回ModelAndView
对象给前端控制器,前端控制器再调用视图解析器ViewResolver
将逻辑视图解析为实际视图,并将模型数据传递给视图View
,视图将其渲染成HTML,返回给前端控制器,前端控制器再将渲染好的视图返回给客户端
常用注解
-
控制器相关
-
@Controller
:类级别的注解,用于标记一个类为Controller的注解,负责接收用户请求,进行处理后,最终返回客户,一般结合@RequestMapping
进行使用 -
@RestController
:是@Controller
和@ResponseBody
的结合,它标记的类中,每个方法的返回值都会以 JSON 或 XML 的形式直接写入 HTTP 响应体中,相当于在每个方法上都添加了 @ResponseBody 注解
-
-
请求映射相关
-
@RequestMapping
:用于将请求映射到相应的处理方法上,可以作用于类或者方法级别 -
@GetMapping
、@PostMapping
、@PutMapping
、@DeleteMapping
Restful 风格
浏览器表单中只支持GET和POST请求,所以为了让浏览器实现PUT和DELETE,Spring提供了一个过滤器,将GET、POST请求转换成PUT、DELETE形式
-
-
参数绑定相关
-
@RequestParam
:将HTTP请求的参数绑定到控制器方法的参数上 -
@PathVariable
:用于获取路径中的动态参数 -
@RequestBody
:将请求体中的JSON或XML数据绑定到方法的参数对象上
-
-
响应相关
-
@ResponseBody
:将方法的返回值作为HTTP响应体的内容,常用于返回JSON或XML数据 -
@ResponseStatus
:用于设置HTTP对应的状态码
-
自定义类型转换器
页面传递的参数是String
类型的,而在控制器中接收的参数是不固定的,对于基本数据类型,SpringMVC提供了类型转换器,对于不支持的目标类型,如:日期、自定义类型,则需要自定义类型转换器
顶层接口:Converter --------- 将类型 S 转换成 T
|---|-------------------------------------------------------------------------------------------------------------|
| | @FunctionalInterface
|
| | public interface Converter<S, T> {
|
| | /**
|
| | * Convert the source object of type {@code S} to target type {@code T}.
|
| | * @param source the source object to convert, which must be an instance of {@code S} (never {@code null})
|
| | * @return the converted object, which must be an instance of {@code T} (potentially {@code null})
|
| | * @throws IllegalArgumentException if the source cannot be converted to the desired target type
|
| | */
|
| | @Nullable
|
| | T convert(S source);
|
| | }
|
实现
通过实现 Converter 接口
- 自定义类型转换器 ------ 实现
Converter
接口
实体类
|---|------------------------------------------------------|
| | public class Goods {
|
| | private String name;
|
| | private double price;
|
| | private int num;
|
| | |
| | public Goods() {}
|
| | public Goods(String name, double price, int num) {
|
| | this.name = name;
|
| | this.price = price;
|
| | this.num = num;
|
| | }
|
| | |
| | @Override
|
| | public String toString() {
|
| | return "Goods{" +
|
| | "name='" + name + '\'' +
|
| | ", price=" + price +
|
| | ", num=" + num +
|
| | '}';
|
| | }
|
| | |
| | public String getName() {
|
| | return name;
|
| | }
|
| | |
| | public void setName(String name) {
|
| | this.name = name;
|
| | }
|
| | |
| | public double getPrice() {
|
| | return price;
|
| | }
|
| | |
| | public void setPrice(double price) {
|
| | this.price = price;
|
| | }
|
| | |
| | public int getNum() {
|
| | return num;
|
| | }
|
| | |
| | public void setNum(int num) {
|
| | this.num = num;
|
| | }
|
| | }
|
Controller层
|---|-----------------------------------------------------------------------|
| | @Controller
|
| | @RequestMapping("/goods")
|
| | public class GoodsController {
|
| | @RequestMapping("/show")
|
| | public String add(@RequestParam("goods") Goods goods, Model model){
|
| | model.addAttribute("goods",goods);
|
| | return "showGoods";
|
| | }
|
| | }
|
自定义类型转换 GoodsConverter
|---|---------------------------------------------------------------------|
| | public class GoodsConverter implements Converter<String, Goods> {
|
| | public GoodsConverter(){
|
| | System.out.println("GoodsConverter...");
|
| | }
|
| | @Override
|
| | public Goods convert(String source) {
|
| | Goods goods = new Goods();
|
| | if (source != null) {
|
| | String[] split = source.split(",");
|
| | if (split.length == 3) {
|
| | goods.setName(split[0]);
|
| | goods.setPrice(Double.parseDouble(split[1]));
|
| | goods.setNum(Integer.parseInt(split[2]));
|
| | return goods;
|
| | } else {
|
| | throw new IllegalArgumentException(String.format(
|
| | "类型转换失败, 需要格式'apple, 10.58,200 ',但格式是[% s ] ", source));
|
| | }
|
| | }else {
|
| | throw new IllegalArgumentException("source不能为空");
|
| | }
|
| | }
|
| | }
|
spring-mvc.xml配置文件
|---|-------------------------------------------------------------------------------------------------------------------|
| | <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
|
| | <property name="converters">
|
| | <list>
|
| | <bean class="com.ry.GoodsConverter"/>
|
| | </list>
|
| | </property>
|
| | </bean>
|
| | <mvc:annotation-driven conversion-service="conversionService"/>
|
通过@InitBinder
注解(推荐)
用于
@Controller
标注的类的方法上,表示为当前控制器注册一个属性编辑器,只对当前Controller有效,在其他方法执行前执行,做到预处理数据;@InitBinder
标注的方法必须有WebDataBinder
参数,用于表单到方法的绑定**(属性编辑器可以理解为帮我们完成参数绑定的)**
|---|-------------------------------------------------------------------------------|
| | @Controller
|
| | public class UserController {
|
| | @InitBinder
|
| | public void initBinder(WebDataBinder binder){
|
| | binder.registerCustomEditor(LocalDate.class,new PropertyEditorSupport(){
|
| | @Override
|
| | public void setAsText(String text) throws IllegalArgumentException {
|
| | //将请求参数转换为LocalDate类型
|
| | setValue(LocalDate.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
| | }
|
| | });
|
| | }
|
| | }
|
拦截器
拦截指定的用户请求,并进行相应的预处理和后处理,类似于Servlet中的过滤器,只不过比过滤器的功能更强大
应用场景
-
**日志记录:**记录请求信息的日志,以便进行信息监控、信息统计等
-
**权限控制:**如登录检测,进入处理器检测是否登录,没有登录返回登录页面
-
**性能监测:**记录拦截器进入处理器和离开处理器的时间
配置及使用
-
通过实现
HandlerInterceptor
接口来实现拦截器,接口中有三个方法preHandle()
、postHandle()
、afterCompletion()
-
preHandle()
:处理器执行前执行,如果返回false ,则跳过处理器、拦截器postHandle()
、视图渲染等,直接执行拦截器afterCompletion()
-
postHandle()
:处理器执行后,视图渲染前执行,如果抛出异常,则跳过该方法直接执行afterCompletion()
-
afterCompletion()
:视图渲染后执行,不管是否抛出异常
|---|--------------------------------------------------------------------------------------------------------------------------------------------------|
| |/**
|
| |* 定义一个拦截器
|
| |*/
|
| |public class HandlerInterceptor1 implements HandlerInterceptor {
|
| |@Override
|
| |public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
| |System.out.println("拦截器1的preHandle方法执行了...");
|
| |//返回值true表示放行,false表示不放行
|
| |return true;
|
| |}
|
| |@Override
|
| |public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
| |System.out.println("拦截器1的postHandle方法执 行了");
|
| |}
|
| |@Override
|
| |public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
| |System.out.println("拦截器1的afterCompletion方法执行了");
|
| |}
|
| |}
| -
-
在SpringMVC配置文件中进行配置
-
在
<mvc:interceptors>
使用<mvc:interceptor>
标签对拦截器进行作用范围的设置。 -
使用
<mvc:mapping path=""/>
设置处理的请求,可以使用通配符,可以设置多个 -
使用
<mvc:exclude-mapping path="" />
设置不需要拦截的请求,可以使用通配符,可以配置多个。但是使用的前提是需要先配置需要处理的请求范围,即需要先配置了<mvc:mapping path="" />
才行,否则会有错误。
|---|------------------------------------------------------------------------------------------------|
| |<!-- 配置拦截器,拦截器可以有0或多个 -->
|
| |<mvc:interceptors>
|
| |<!-- 配置一个拦截器 -->
|
| |<mvc:interceptor>
|
| |<!-- mvc:mapping用于指定当前所注册的拦截器可以拦截的请求路径,path路径/**表示拦截所有请求,两个*表示匹配多级目录URL地址,例如/aaa/bbb/ccc -->
|
| |<mvc:mapping path="/**"/>
|
| |<bean id="handlerInterceptor1" class="com.thr.interceptor.HandlerInterceptor1"/>
|
| |</mvc:interceptor>
|
| |</mvc:interceptors>
| -
多个拦截器的执行顺序
当我们在项目中配置了多个拦截器,他们的执行顺序由我们定义时的顺序决定,但是拦截器中的方法执行顺序不同
先顺序执行拦截器1、2、3的
preHandle()
,再逆序执行postHandle()
,最后逆序执行afterCompletion()
方法
拦截器和过滤器的区别
-
拦截器不依赖于 Servlet 容器,过滤器依赖于 Servlet 容器
-
拦截器是基于Java反射的,过滤器是基于函数回调
-
拦截器只对action请求起作用,过滤器对几乎所以请求都有效
-
拦截器可以访问action上下文、值栈中的对象,过滤器不能访问