3.2.2.SpringMVC简介

3.2.2.SpringMVC

Spring MVC(Model-View-Controller)是一个用于构建Web应用程序的框架,属于Spring Framework的一部分。它采用了MVC设计模式,并通过Spring提供的各种功能来管理Web应用程序的请求、响应、控制和展示逻辑。Spring MVC的设计理念是解耦(Decoupling),即将业务逻辑、显示层、数据存储层等模块分开,使得各个模块之间的依赖最小化。

3.2.2.1.Spring MVC架构设计

Spring MVC采用经典的MVC(Model-View-Controller)设计模式:

Model(模型):负责处理业务逻辑和数据。通常,Model是一个POJO(Plain Old Java Object),代表应用程序的业务对象。在Spring MVC中,Model是传递给视图的数据部分。

View(视图):负责将Model中的数据呈现给用户,通常是JSP、Thymeleaf、FreeMarker等技术。View呈现最终的用户界面。

Controller(控制器):充当中介者,接收用户的请求,执行业务逻辑,并返回视图。它从Model获取数据,并将该数据传递给View。

3.2.2.2.Spring MVC整体流程

Spring MVC使用一个请求-响应的流程进行工作,基本流程如下:

客户端请求:浏览器发出HTTP请求,通常是通过URL(如 /home)发出。

DispatcherServlet接收请求:所有的请求都通过DispatcherServlet,它是Spring MVC的前端控制器。

请求映射到控制器:DispatcherServlet根据URL和映射配置,通过HandlerMapping来找到对应的控制器方法。

控制器处理请求:控制器处理请求,执行业务逻辑,并将数据放入Model中。

视图解析:返回的ModelAndView对象中包含了数据和视图名称,DispatcherServlet将根据视图名称通过ViewResolver解析得到最终视图。

渲染视图并返回响应:最后,DispatcherServlet将Model渲染到视图中,并通过HTTP响应返回给客户端。

3.2.2.3.Spring MVC核心组件

Spring MVC的核心组件是:DispatcherServlet、HandlerMapping、Controller、ModelAndView、ViewResolver等。每个组件在框架中都扮演着重要角色,下面将详细介绍它们。

1)DispatcherServlet(前端控制器)

DispatcherServlet是Spring MVC的核心,是所有请求的入口。它作为前端控制器(Front Controller)接收客户端请求,并将请求分发到相应的处理器(Controller)。

功能:

请求接收:接收来自客户端的HTTP请求。

请求分发:根据请求的URL和配置,选择合适的控制器来处理请求。

视图解析:请求处理后,返回视图(如JSP、HTML、JSON等),将数据填充到视图中。

2)HandlerMapping(请求映射)

HandlerMapping负责将用户的请求URL映射到具体的控制器方法。Spring MVC提供了多个HandlerMapping的实现来支持不同类型的请求映射:

AnnotationMethodHandlerMapping:通过@RequestMapping等注解映射请求。

SimpleUrlHandlerMapping:根据URL将请求映射到处理器Bean。

BeanNameUrlHandlerMapping:根据Bean的名称进行映射。

常见的映射方法:

@RequestMapping:最常用的映射注解,用于映射请求的URL。

@GetMapping / @PostMapping / @PutMapping / @DeleteMapping:分别映射GET、POST、PUT、DELETE请求。

示例:

|------------------------------------------------------------------------------------------------------------------|
| @Controller public class MyController { @RequestMapping("/hello") public String sayHello() { return "hello"; } } |

3)Controller(控制器)

控制器是Spring MVC的核心组件,负责处理用户的请求,执行相应的业务逻辑,并将数据传递给视图。控制器通常是一个Java类,带有@Controller注解,并包含多个请求处理方法。

常用注解:

@Controller:标注为控制器类,负责处理用户请求。

@RequestMapping:标注方法来处理指定URL的请求。

@RestController:是@Controller和@ResponseBody的组合,主要用于RESTful服务。

@RequestParam:用于接收请求参数。

@PathVariable:从路径中提取参数。

@ModelAttribute:将表单数据绑定到Java对象。

示例:

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Controller public class UserController { @RequestMapping("/user/{id}") public String getUser(@PathVariable("id") int userId, Model model) { User user = userService.getUserById(userId); model.addAttribute("user", user); return "userDetail"; } } |

4)ModelAndView(模型与视图)

ModelAndView是Spring MVC中的一个重要对象,用来封装模型数据和视图信息。它包含两部分内容:

Model:封装业务数据(如通过model.addAttribute()传递的对象)。

View:指定渲染的视图(如JSP、HTML等)。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @RequestMapping("/hello") public ModelAndView sayHello() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("hello"); modelAndView.addObject("message", "Hello, Spring MVC!"); return modelAndView; } |

5)ViewResolver(视图解析器)

ViewResolver用于根据控制器返回的视图名称解析出最终的视图资源。Spring MVC提供了几种常见的视图解析器:

InternalResourceViewResolver:基于JSP的视图解析器。

ThymeleafViewResolver:支持Thymeleaf模板的视图解析器。

FreeMarkerViewResolver:支持FreeMarker的视图解析器。

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Bean public InternalResourceViewResolver viewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/views/"); resolver.setSuffix(".jsp"); return resolver; } |

6)HandlerInterceptor(处理器拦截器)

HandlerInterceptor用于在请求处理过程的不同阶段执行逻辑,例如在请求到达控制器之前、控制器处理之后、视图渲染之前等。

常用方法:

preHandle():请求处理之前执行。

postHandle():请求处理之后执行,但在视图渲染之前。

afterCompletion():视图渲染之后执行,清理资源。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("Request pre-handling"); return true; // return true to continue the request, false to stop } } |

3.2.2.4.Spring MVC的优缺点

优点:

松耦合设计:Spring MVC通过分离控制器、模型和视图,使得各个模块的耦合度较低,便于维护和扩展。

灵活性:Spring MVC支持多种视图技术(JSP、Thymeleaf、FreeMarker等),并且具有强大的扩展性,可以与其他Spring模块无缝集成。

支持RESTful架构:Spring MVC提供了非常好的支持RESTful Web服务的能力,通过@RestController等注解来简化RESTful API开发。

强大的注解支持:通过注解可以轻松进行请求映射、参数绑定、异常处理等操作,使得代码更加简洁和清晰。

缺点:

配置繁琐:Spring MVC的配置文件较多,尤其是早期的XML配置方式可能使得项目配置较为繁杂。

学习曲线较高:Spring MVC的概念较为抽象,尤其是对于初学者,可能需要一定的时间来理解其工作流程。

性能开销:由于其基于Servlet的模型,性能可能不如某些轻量级框架(如Spring Boot嵌入式服务器)或者原生Servlet实现。

3.2.2.5.管理SpringBean

在 Spring 中,Spring Bean 的管理 是核心的一部分。它涉及如何创建、配置、注入和销毁应用程序中的各种组件(Bean)。Spring 提供了强大且灵活的机制来管理 Bean 生命周期、作用域和依赖注入。做好 Spring Bean 的管理不仅有助于优化代码结构,也能提升系统的可维护性和扩展性。

1)合理使用 Spring Bean 的作用域(Scope)

Spring 提供了不同的作用域来管理Bean实例的生命周期。根据需求,选择合适的作用域非常重要。

singleton(单例):默认作用域。Spring 容器启动时会创建一个单例对象,并在整个应用程序的生命周期中共享这个实例。适用于无状态的 Bean。

prototype(原型):每次请求都会创建一个新的 Bean 实例。适用于有状态的 Bean,或者需要动态创建的 Bean。

request:在 Web 应用程序中,每一个 HTTP 请求都会创建一个新的 Bean 实例。适用于请求级别的 Bean。

session:每一个 HTTP 会话都会创建一个新的 Bean 实例。适用于会话级别的 Bean。

application:类似于 singleton,但是针对每个 Spring 容器实例,适用于上下文级别的管理。

使用示例:

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Component @Scope("singleton") // 默认作用域 public class MySingletonBean { // ... 业务逻辑 } @Component @Scope("prototype") // 每次注入时都创建新的实例 public class MyPrototypeBean { // ... 业务逻辑 } |

2)合理使用依赖注入(DI)

Spring 提供了两种常用的依赖注入方式:构造器注入和Setter 注入。构造器注入被认为是更推荐的方式,尤其是在涉及到不可变依赖时。

构造器注入:使用构造函数注入依赖。这种方式的好处是可以让 Bean 在创建时就注入依赖,确保依赖是不可变的。

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Component public class MyService { private final MyRepository repository; @Autowired public MyService(MyRepository repository) { this.repository = repository; } // 使用 repository } |

Setter 注入:通过 setter 方法来注入依赖。适合于可选的依赖,或当依赖关系较复杂时。

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Component public class MyService { private MyRepository repository; @Autowired public void setRepository(MyRepository repository) { this.repository = repository; } // 使用 repository } |

字段注入:通过直接在字段上注入依赖。这种方式很简洁,但不推荐用于必须初始化的依赖。

|-----------------------------------------------------------------------------------|
| @Component public class MyService { @Autowired private MyRepository repository; } |

推荐做法:

优先使用 构造器注入,可以确保依赖是不可变的,有助于避免空指针异常。

Setter 注入 适合于可选依赖。

避免过多使用 字段注入,因为它隐藏了依赖关系,降低了代码可读性和可测试性。

3)使用 @Qualifier 解决 Bean 注入冲突

如果存在多个类型相同的 Bean 时,Spring 会不知道注入哪个 Bean,这时可以使用 @Qualifier 来指定要注入的具体 Bean。

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Component public class MyService { private final MyRepository repository; @Autowired public MyService(@Qualifier("myRepositoryImpl") MyRepository repository) { this.repository = repository; } } |

@Qualifier 注解与 @Autowired 配合使用,可以指定需要注入的具体 Bean 名称。

4)合理使用 @Primary

如果有多个候选 Bean 可以注入,并且你希望 Spring 自动注入其中一个作为默认选择,可以使用 @Primary 注解。

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Component @Primary public class PrimaryRepository implements MyRepository { // 实现方法 } @Component public class SecondaryRepository implements MyRepository { // 实现方法 } |

如果没有使用 @Qualifier 注解,Spring 会默认选择 @Primary 注解的 Bean。

5)初始化和销毁方法

在 Bean 的生命周期中,有时需要进行初始化和销毁操作。Spring 提供了两种方式来处理这些操作:

使用 @PostConstruct 和 @PreDestroy 注解。

|---------------------------------------------------------------------------------------------------------------------------------|
| @Component public class MyBean { @PostConstruct public void init() { // 初始化逻辑 } @PreDestroy public void cleanup() { // 销毁逻辑 } } |

使用 InitializingBean 和 DisposableBean 接口。

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Component public class MyBean implements InitializingBean, DisposableBean { @Override public void afterPropertiesSet() throws Exception { // 初始化逻辑 } @Override public void destroy() throws Exception { // 销毁逻辑 } } |

使用 @Bean 注解的 initMethod 和 destroyMethod 属性(适用于 Java 配置方式)。

|-------------------------------------------------------------------------------------------------------------------------------------------------|
| @Configuration public class AppConfig { @Bean(initMethod = "init", destroyMethod = "cleanup") public MyBean myBean() { return new MyBean(); } } |

这些方法可以帮助你在 Bean 的生命周期中执行一些初始化和销毁操作,确保资源的正确释放。

6)使用 @Profile 管理环境配置

当你在不同的环境中使用 Spring 应用(例如开发、测试、生产环境)时,可以使用 @Profile 注解来管理 Bean 的激活状态。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Component @Profile("dev") public class DevService implements MyService { // 开发环境的实现 } @Component @Profile("prod") public class ProdService implements MyService { // 生产环境的实现 } |

然后在配置文件中设置激活的 profile:

application.properties

|----------------------------|
| spring.profiles.active=dev |

使用 @Profile 可以让你的 Spring 应用根据不同的环境注入不同的 Bean,提高灵活性。

7)避免过度注入和过度依赖

避免过度注入:当一个 Bean 的依赖项过多时,可能是设计上的问题。尽量遵循 单一职责原则,拆分过大的类和过多依赖。

避免过度依赖:避免在类中注入大量的服务。可以通过 Service Layer 或 Facade 模式来简化依赖关系。

8)AOP 管理 Bean 的行为

使用 AOP(面向切面编程) 可以帮助你对 Bean 的方法进行拦截和处理,增加日志、事务等功能,而不必修改原始代码。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Aspect @Component public class LoggingAspect { @Before("execution(* com.example.service.*.*(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("Before method: " + joinPoint.getSignature().getName()); } } |

通过 AOP 管理 Bean 的横切关注点,可以提高代码的复用性和解耦性。

3.2.2.6.避免重复Bean

在 Spring 中,避免重复的 Bean 是一个重要的设计考量,尤其是在复杂的项目中。重复的 Bean 会导致依赖注入错误、程序逻辑不清晰,甚至可能引发 NoUniqueBeanDefinitionException 错误(当存在多个相同类型的 Bean 时)。为了有效避免重复 Bean 的问题,可以采取以下几种策略:

1)使用 @Qualifier 注解指定 Bean 名称

当有多个类型相同的 Bean 时,Spring 会默认选择第一个匹配的 Bean。为了明确指定使用哪个 Bean,可以使用 @Qualifier 注解指定具体的 Bean 名称。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Component("beanA") public class MyBeanA implements MyBean { // Bean A 实现 } @Component("beanB") public class MyBeanB implements MyBean { // Bean B 实现 } @Component public class MyService { @Autowired @Qualifier("beanA") private MyBean myBean; // 明确指定注入 beanA } |

优点:

使用 @Qualifier 注解,可以显式地告诉 Spring 使用哪个 Bean,避免了多个同类型 Bean 的冲突。

注意:

@Qualifier 必须和 @Autowired 一起使用,否则 Spring 无法知道注入哪个 Bean。

2)使用 @Primary 注解指定默认 Bean

当你有多个同类型的 Bean 时,可以通过 @Primary 注解指定一个默认的 Bean。当没有明确指定的情况下,Spring 会选择 @Primary 注解的 Bean。

|--------------------------------------------------------------------------------------------------------------------------------------|
| @Component @Primary public class MyBeanA implements MyBean { // 实现 A } @Component public class MyBeanB implements MyBean { // 实现 B } |

优点:

@Primary 注解让你在多个同类型的 Bean 中指定一个默认的 Bean。

可以减少使用 @Qualifier 的需求,简化配置。

注意:

仅适用于没有明确指定的场景,如果明确使用 @Qualifier,@Primary 不会生效。

3)避免 Bean 的重复定义(使用唯一 Bean 名称)

在 Spring 中,Bean 的名字默认是类名的首字母小写。确保在 @Component、@Service、@Repository 等注解中指定唯一的 Bean 名称,避免多个 Bean 使用相同名称。

|-------------------------------------------------------------------|
| @Component("myUniqueBeanName") public class MyService { // 业务逻辑 } |

优点:

保证了 Bean 的名称唯一性,避免了重复的 Bean 名称问题。

注意:

在复杂的项目中,建议使用规范的命名策略来管理 Bean 名称,避免出现冲突。

4)使用 @Profile 来分离不同环境的 Bean 定义

如果你在开发、测试和生产环境中需要使用不同的 Bean,可以通过 @Profile 注解来管理 Bean 的加载,避免不同环境中重复加载相同的 Bean。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Component @Profile("dev") public class DevService implements MyService { // 开发环境的实现 } @Component @Profile("prod") public class ProdService implements MyService { // 生产环境的实现 } |

优点:

@Profile 可以帮助你在不同的环境中加载不同的 Bean,避免不同环境中的重复 Bean。

注意:

使用@Profile 时,确保配置文件中正确指定了激活的 Profile,例如:spring.profiles.active=dev。

5)避免多个配置文件定义相同 Bean

在使用 Java 配置方式时,可能会在不同的 @Configuration 文件中定义相同类型的 Bean。确保在配置类中没有重复定义相同的 Bean。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Configuration public class ConfigA { @Bean public MyService myServiceA() { return new MyServiceImpl(); } } @Configuration public class ConfigB { @Bean public MyService myServiceB() { return new MyServiceImpl(); } } |

这种配置容易引发 Bean 的重复定义错误,尤其是当两个配置文件中都定义了相同类型的 Bean 时。为了解决这个问题,可以:

使用不同的名称来区分 Bean。

使用条件注解(如 @Profile)来确保只在特定环境中加载某个 Bean。

6)使用 @Import 来导入 Bean 定义

当你有多个配置类时,确保每个配置类的责任清晰且不会重复。如果需要将多个配置类合并,可以使用 @Import 注解导入其他配置类。

|------------------------------------------------------------------------------|
| @Configuration @Import(OtherConfig.class) public class MainConfig { // 主配置 } |

优点:

@Import 可以帮助你清晰地管理多个配置类,避免重复 Bean 定义。

7)手动注册 Bean 时检查重复

如果你使用了 AnnotationConfigApplicationContext 或 GenericWebApplicationContext 等手动注册 Bean 的方式,在注册 Bean 时,要特别注意检查是否存在重复定义的情况。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(MyConfig.class); context.refresh(); // 检查是否有重复的 Bean if (context.getBeansOfType(MyService.class).size() > 1) { throw new IllegalStateException("存在多个 MyService Bean 定义!"); } |

优点:

这种方法可以帮助你在程序启动时主动检查是否有重复的 Bean 定义。

8)避免自动扫描重复包路径

当使用组件扫描时,确保没有扫描到重复的包路径。如果你的项目结构很复杂,可能会无意中扫描到重复的包或类,导致同样的 Bean 被创建多次。可以通过 @ComponentScan 来指定扫描的范围,避免重复扫描。

|-------------------------------------------------------|
| @ComponentScan(basePackages = "com.example.services") |

优点:

通过限制 @ComponentScan 的范围,避免多次扫描相同的 Bean 类。

9)检查第三方库中的 Bean 定义

如果你使用了第三方库(如 Spring Boot Starter),它们有时会自动注册一些 Bean。如果这些 Bean 与你的应用中的 Bean 重名或类型相同,可能会引发冲突。可以通过 @Primary 或 @Qualifier 解决,或者在配置文件中禁用某些自动装配。

10)避免不必要的注入

避免过度注入 Bean,特别是在某些类中注入大量不同类型的 Bean。如果一个类有过多的依赖项,它可能是一个 过度设计的类,应考虑重新设计或者拆分类。

11)必要时使用文档来记录、管理Bean

参考:Bean管理文档

相关推荐
朝新_6 天前
【SpringMVC】SpringMVC 请求与响应全解析:从 Cookie/Session 到状态码、Header 配置
java·开发语言·笔记·springmvc·javaee
赋能大师兄6 天前
Spring MVC配置解决跨域请求
springmvc·cors·简单请求·非简单请求
天若有情67314 天前
Spring MVC文件上传与下载全面详解:从原理到实战
java·spring·mvc·springmvc·javaee·multipart
李贺梖梖15 天前
SpringMVC初始
springmvc
Cyan_RA92 个月前
SpringMVC @RequestMapping的使用演示和细节 详解
java·开发语言·后端·spring·mvc·ssm·springmvc
Cyan_RA92 个月前
SpringMVC 执行流程分析 详解(图解SpringMVC执行流程)
java·人工智能·后端·spring·mvc·ssm·springmvc
FrankYoou2 个月前
Spring Boot + Spring MVC 项目结构
spring boot·spring·springmvc
FrankYoou2 个月前
Spring MVC + JSP 项目的配置流程,适合传统 Java Web 项目开发
java·spring·springmvc
YDS8292 个月前
SpringMVC —— Spring集成web环境和SpringMVC快速入门
java·spring·mvc·springmvc