Java的SpringMVC

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 接口

  1. 自定义类型转换器 ------ 实现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上下文、值栈中的对象,过滤器不能访问

相关推荐
小万编程1 小时前
【2025最新计算机毕业设计】基于SSM的医院挂号住院系统(高质量源码,提供文档,免费部署到本地)【提供源码+答辩PPT+文档+项目部署】
java·spring boot·毕业设计·计算机毕业设计·项目源码·毕设源码·java毕业设计
白宇横流学长1 小时前
基于Java的银行排号系统的设计与实现【源码+文档+部署讲解】
java·开发语言·数据库
123yhy传奇1 小时前
【学习总结|DAY027】JAVA操作数据库
java·数据库·spring boot·学习·mybatis
想要打 Acm 的小周同学呀1 小时前
亚信科技Java后端外包一面
java·求职·java后端
lishiming03085 小时前
TestEngine with ID ‘junit-jupiter‘ failed to discover tests 解决方法
java·junit·intellij-idea
HEU_firejef5 小时前
设计模式——工厂模式
java·开发语言·设计模式
Kobebryant-Manba5 小时前
单元测试学习2.0+修改私有属性
java·单元测试·log4j
fajianchen5 小时前
应用架构模式
java·开发语言
Code成立5 小时前
《Java核心技术 卷II》流的创建
java·开发语言·流编程
张敬之、6 小时前
SpringCloud源码分析-nacos与eureka
java·spring cloud·eureka