框架-SpringMVC-1
目录
1、简述SpringMVC框架的作用
SpringMVC的优点:
附录1: 什么是MVC模式?
附录2:什么是Spring MVC
2、简述SpringMVC工作原理
①、SpringMVC工作流程
②、SpringMVC中的五/六大组件
4、SpringMVC常用的注解有哪些
1、@Controller
2、@RestController
1、@RequestMapping
2、@Get/Post/DeleteMapping
1、@RequestParam
2、@PathVariable
3、@RequestBody
4、@RequestHeader
5、@CookieValue
1、@ResponseBody
2、@ResponseStatus
1、@ModelAttribute
2、@SessionAttributes
1、@ControllerAdvice
2、@ExceptionHandler
附录1:JDK原生注解
5、SpringMVC怎么设定重定向和转发的
①、重定向(Redirect):
②、转发(Forward):
6、Spring和SpringMVC为什么需要父子容器?
7、SpringMVC的拦截器和过滤器有什么区别?执行顺序?
8、Spring是如何保证 Controller并发的安全?
①. 无状态设计
②. 避免使用可变的实例变量
③. 使用ThreadLocal为线程提供独立的变量副本
1、简述SpringMVC框架的作用
SpringMVC是一种创建Web应用程序的框架,主要起到以下几个作用:
①、提供一个模型视图控制器(MVC)框架,可以帮助开发人员更好的分离业务逻辑,模型对象,视图模板和其他模型组件。
②、提供一种精细的处理用户输入的控制器,这使得开发人员可以以简单的方式处理web请求,同时支持RESTful风格请求。
③、提供一种模型和视图的轻量级容器,可以帮助开发人员更加高效地处理应用程序。
④、提供很好的客户端支持,可以支持多种浏览器,包括Internet Explorer、Mozilla Firefox和Google Chrome等
⑤、支持多种语言,可以轻松地与JavaScript、HTML、CSS和XML等语言协同工作。
SpringMVC的优点:
(1)可以支持各种视图技术,而不仅仅局限于JSP;
(2)与Spring框架集成(如IOC容器、AOP等);
(3)清晰的角色分配:前端控制器(dispatcherServlet) ,请求到处理器映射(handlerMapping),处理器适配器(HandlerAdapter),视图解析器(ViewResolver)。
(4)支持各种请求资源的映射策略。
(5)它较Struts更简单、更安全。
附录1: 什么是MVC模式?
MVC:MVC是一种设计模式分析:
M-Model 模型(完成业务逻辑:有javaBean构成,service+dao+entity)
V-View 视图(做界面的展示 jsp,html……)
C-Controller 控制器(接收请求—>调用模型—>根据结果派发页面)
附录2:什么是Spring MVC
Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
2、简述SpringMVC工作原理
①、SpringMVC工作流程
(≈ 详细描述下SpringMVC中DispatcherServlet的工作流程)
-
用户发送请求至前端控制器DispatcherServlet。
-
DispatcherServlet收到请求调用HandlerMapping处理器映射器。
-
处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
-
DispatcherServlet调用HandlerAdapter处理器适配器。
-
HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
-
Controller执行完成返回ModelAndView。
-
HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
-
DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
-
ViewReslover解析后返回具体View。
-
DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
-
DispatcherServlet响应用户。
②、SpringMVC中的五/六大组件
-
DispatcherServlet(前端控制器)
-
作为整个流程控制的中心,控制其他组件执行,统一调度,降低组件之间的耦合度,提高每个组件的扩展性。
-
接收请求,响应结果,相当于转发器,中央处理器。
-
用户请求到达前端控制器,它相当于MVC中的C,DispatcherServlet是整个流程控制的中心,由它调用其他组件处理用户的请求。由框架提高
-
-
HandlerMapping(处理器映射器)
-
根据请求的url查找handler:HandlerMapping负责根据用户请求找到Handler即处理器,SpringMVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
-
-
HandlerAdapter(处理器适配器)
-
按照特定规则(HandlerAdapter要求的规则)去执行Handler:通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
-
-
Handler(处理器)
-
编写Handler时按照HandlerAdapter的要求去做,这样的适配器才可以去正确执行Handler
-
Handler是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。由于Handler涉及到具体的用户业务请求,所以一般情况下需要工程师根据业务需求开发Handler.
-
-
View resolver(视图解析器)
-
进行视图解析,根据逻辑视图名解析成真正的视图(View)
-
View Resolver负责将处理结果生产View视图,View Resolver首先根据逻辑视图名解析成物理视图名(即具体的页面地址),再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。
-
SpringMVC框架提高了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。
-
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。
-
-
View
-
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf...)
-
4、SpringMVC常用的注解有哪些
①、控制器定义
1、@Controller
标记为SpringMVC控制器,处理Http请求并返回视图名称,一般配合@RequestMapping
等注解定义请求映射规则。
RESTful API:配合@ResponseBody
或直接使用@RestController
返回JSON数据
@Controller + @ResponseBody +
@RequestMapping(请求路径)
@Controller
@RequestMapping("/user")
public class UserController {@GetMapping("/data")@ResponseBodypublic Map<String, String> getData() {return Map.of("status", "success");}
}
2、@RestController
是Spring框架中用于构建RESTful Web服务的核心注解,本质上是@Controller
和@ResponseBody
的组合注解
@RestController = @Controller + @ResponseBody
@RestController +
@RequestMapping(请求路径)=
@Controller + @ResponseBody +
@RequestMapping(请求路径)
@RestController
@RequestMapping("/user")
public class UserController {@GetMapping("/data")public Map<String, String> getData() {return Map.of("status", "success");}
}
②、请求映射
1、@RequestMapping
(1)、作用
@RequestMapping
是Spring MVC框架中用于建立HTTP请求与控制器方法映射关系的核心注解,支持类级别和方法级别的路径绑定。
作用1:定义URL路径规则,将特定请求路由到对应的处理方法。
作用2:支持限定HTTP方法(GET/POST等)、请求参数、请求头等条件
(2),作用域
类级别:定义控制器的基础请求路径。
方法级别:定义具体处理逻辑的路径和HTTP方法
(3),关键参数
@RequestMapping(value = "/search",method = {RequestMethod.GET, RequestMethod.POST},params = "keyword",headers = "X-API-Version=1.0"
)
public String search(@RequestParam String keyword) {return "搜索结果页"; // 仅匹配带keyword参数且版本为1.0的GET/POST请求:ml-citation{ref="3,9" data="citationList"}
}
2、@Get/Post/DeleteMapping
@GetMapping
:专用于处理HTTP GET请求的注解,通常用于数据检索或页面导航,不会改变服务器状态。
@PostMapping
:专用于处理HTTP POST请求的注解,适用于数据提交或资源创建,如表单提交或API数据新增
它们是@RequestMapping的特定请求方式的简写版
@GetMapping = @RequestMapping(method = RequestMethod.GET)
③、参数处理
1、@RequestParam
作用:从请求参数(URL查询字符串或表单数据)中提取值,适用于GET/POST请求
关键属性:
value/name
:参数名(必填)
required
:是否必传(默认true
)
defaultValue
:默认值(设置后required
自动为false
)
请求示例: /search?keyword=Spring&page=2@GetMapping("/search")
public String search(@RequestParam("keyword") String keyword, @RequestParam(value = "page", defaultValue = "1") int page) {return "搜索关键词: " + keyword + ",页码: " + page;
}
2、@PathVariable
作用:从URL路径模板中提取变量值,常用于RESTful接口。
关键属性:value/name
:路径变量名(若与方法参数名一致可省略)
请求示例:/users/101@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {User user = userService.findById(id);return ResponseEntity.ok(user);
}
3、@RequestBody
作用:将请求体(如JSON/XML)反序列化为Java对象,通常用于POST/PUT请求。
限制:仅支持非GET
请求,且需指定Content-Type
为application/json
等。
需发送JSON请求体:{"name": "Alice", "age": 25}@PostMapping("/users")
public ResponseEntity<String> createUser(@RequestBody User user) {userService.save(user);return ResponseEntity.ok("创建成功");
}
4、@RequestHeader
作用:从HTTP请求头中获取指定字段值
关键属性:value/name
:请求头字段名(如User-Agent
)
//自动从请求头User-Agent提取值@GetMapping("/info")
public String getInfo(@RequestHeader("User-Agent") String userAgent) {return "客户端浏览器: " + userAgent;
}
5、@CookieValue
作用:从HTTP Cookie中提取值。
关键属性:value/name
:Cookie名称(如JSESSIONID
)
//从Cookie中获取JSESSIONID值@GetMapping("/session")
public String getSessionId(@CookieValue("JSESSIONID") String sessionId) {return "当前会话ID: " + sessionId;
}
④、响应处理
1、@ResponseBody
作用:将方法返回值直接绑定到HTTP响应体中(而非视图解析),通常用于返回JSON/XML等数据格式。
适用场景:RESTful API开发,前后端分离架构中返回结构化数据。
底层原理: 通过HttpMessageConverter
实现序列化(如MappingJackson2HttpMessageConverter
处理JSON)。默认依赖Jackson库(需添加相关依赖,如Spring Boot中自动包含)。
@RestController = @Controller + @ResponseBody
//示例1:返回JSON数据(类级别@RestController)@RestController
@RequestMapping("/api")
public class UserController {@GetMapping("/user/{id}")public User getUser(@PathVariable Long id) {return new User(id, "Alice", 25); // 自动转为JSON}
}//示例2:返回字符串(方法级别@ResponseBody)
@Controller
public class MessageController {@ResponseBody@GetMapping("/hello")public String sayHello() {return "Hello, World!"; // 直接返回文本}
}//示例3:返回自定义HTTP状态码
@RestController
public class TaskController {@PostMapping("/tasks")public ResponseEntity<Task> createTask(@RequestBody Task task) {Task savedTask = taskService.save(task);return ResponseEntity.status(HttpStatus.CREATED).body(savedTask);}
}
2、@ResponseStatus
核心功能:
用于为控制器方法或异常类静态绑定HTTP状态码,无需手动操作HttpServletResponse
。
支持通过reason
属性返回错误描述(但REST API中建议用ResponseEntity
替代以避免HTML错误页)
适用场景:
异常类:自定义异常触发时自动返回指定状态码(如404 Not Found
)
控制器方法:显式声明方法执行后的响应状态(如201 Created
)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class InvalidInputException extends RuntimeException {public InvalidInputException(String message) {super(message); // 消息会作为响应体返回}
}@PostMapping("/tasks")
@ResponseStatus(HttpStatus.CREATED)
public Task createTask(@RequestBody Task task) {return taskService.save(task); // 成功时返回201状态
}
⑤、模型与会话
1、@ModelAttribute
核心功能:
①、数据绑定:将请求参数(如表单字段、查询参数)自动绑定到Java对象
②、模型初始化:在控制器方法执行前向模型(Model)中添加属性,供视图层使用
③、支持两种使用场景:
1、方法级别:标注在方法上,用于预加载模型数据
2、参数级别:标注在方法参数上,用于接收绑定的对象
设计初衷:
简化表单处理和视图渲染的数据传递流程,避免手动操作HttpServletRequest
或Model
对象
@Controller
public class ProductController {@ModelAttribute("categories")public List<String> loadCategories() {return Arrays.asList("Electronics", "Books", "Clothing");}@GetMapping("/products")public String showProducts() {return "productList"; // 视图中可直接访问categories属性}
}//loadCategories()方法会在showProducts()执行前调用,结果存入Model的categories属性
@PostMapping("/register")
public String registerUser(@ModelAttribute User user) {userService.save(user);return "success";
}//表单字段名需与User类属性名匹配(如<input name="username">绑定到user.username)
@PostMapping("/update")
public String update(@ModelAttribute("emp") Employee employee) {// 绑定到Model中的"emp"属性而非默认的"employee"return "result";
}
@RequestMapping("/edit")
@ModelAttribute("user")
public User editUser(@RequestParam Long id) {return userService.findById(id); // 返回值直接作为Model属性
}//此时返回值不作为视图名,而是模型数据
@PostMapping("/save")
public String save(@Valid @ModelAttribute User user, BindingResult result) {if (result.hasErrors()) {return "error";}return "success";
}
2、@SessionAttributes
核心功能:
用于将控制器方法中Model
对象的属性自动存储到HTTP会话(Session)中,实现跨请求的数据共享
通常用于多步骤表单、向导式流程等需要暂存数据的场景
与原生HttpSession
的区别:
声明式绑定:通过注解直接指定需共享的属性名或类型,无需手动调用session.setAttribute()
作用域限定:仅在当前控制器类内生效,而非全局Session
//showForm()方法中的user对象会被存入Session,后续请求可通过@ModelAttribute获取@Controller
@SessionAttributes("user") // 将Model中的"user"属性存入Session
public class UserController {@GetMapping("/form")public String showForm(Model model) {model.addAttribute("user", new User()); // 自动同步到Sessionreturn "form";}@PostMapping("/submit")public String submitForm(@ModelAttribute("user") User user, SessionStatus status) {userService.save(user);status.setComplete(); // 清除Session中的"user"return "success";}
}
//所有Cart和Order类型的Model属性均会存入Session@Controller
@SessionAttributes(types = {Cart.class, Order.class}) // 存储所有Cart和Order类型属性
public class ShopController {@PostMapping("/addToCart")public String addToCart(Model model) {model.addAttribute(new Cart()); // 自动同步到Sessionreturn "cart";}
}
@Controller
@SessionAttributes(value = {"profile"}, types = {Settings.class})
public class AccountController {// 同时指定属性名和类型
}
⑥、全局处理
1、@ControllerAdvice
核心功能:
作为控制器增强器,为多个Controller
提供统一的异常处理、数据绑定和预处理逻辑,避免代码重复
本质是@Component
的特殊化实现,通过AOP思想拦截控制器方法
主要用途:
全局异常处理:配合@ExceptionHandler
捕获并统一处理异常
全局数据绑定:通过@ModelAttribute
向所有控制器注入公共数据
请求参数预处理:结合@InitBinder
自定义参数解析规则
//@RestControllerAdvice简化REST接口的异常响应
//ResultVO为统一响应封装类,包含code和data字段
@RestControllerAdvice // 等效于@ControllerAdvice + @ResponseBody
// 限定包路径
@ControllerAdvice(basePackages = "com.example.controllers")
// 限定注解类型
@ControllerAdvice(annotations = RestController.class)
//捕获NullPointerException并返回400状态码@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(NullPointerException.class)public ResponseEntity<String> handleNullPointer(NullPointerException ex) {return ResponseEntity.badRequest().body("空指针异常: " + ex.getMessage());}
}
//向所有请求的Model中添加version属性@ControllerAdvice
public class GlobalDataAdvice {@ModelAttribute("version")public String appVersion() {return "v1.0.0"; // 所有控制器可通过Model获取version}
}
@ControllerAdvice
public class BinderAdvice {@InitBinderpublic void initBinder(WebDataBinder binder) {binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));}
}
//@RestControllerAdvice简化REST接口的异常响应
//ResultVO为统一响应封装类,包含code和data字段@RestControllerAdvice // 等效于@ControllerAdvice + @ResponseBody
public class RestGlobalAdvice {// 处理自定义业务异常@ExceptionHandler(BusinessException.class)public ResultVO<Object> handleBusinessException(BusinessException ex) {return ResultVO.error(ex.getCode(), ex.getMessage());}// 注入全局配置@ModelAttribute("config")public Map<String, String> loadConfig() {return Map.of("env", "prod", "timeout", "30s");}
}
2、@ExceptionHandler
核心功能
①、用于在Spring MVC中集中处理控制器方法抛出的异常,避免在每个方法中重复编写try-catch
逻辑
②、通过声明式方式将异常类型与处理方法绑定,提升代码可维护性和可读性
设计目标
①、统一异常响应:标准化错误信息格式(如JSON或错误页面)
②、异常分类处理:支持按异常类型匹配不同的处理方法
语法:
@ExceptionHandler({异常类.class})
public 返回值类型 方法名(异常类型 ex) { ... }
避免过度捕获:优先处理具体异常,最后用Exception.class
兜底
代码示例:
①、处理单一异常(控制器内)
当getUser()
抛出IllegalArgumentException
时,自动调用handleIllegalArg()
返回400错误
@RestController
public class UserController { @GetMapping("/user/{id}") public User getUser(@PathVariable Long id) { if (id == null) throw new IllegalArgumentException("ID不能为空"); return userService.findById(id); } @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity<String> handleIllegalArg(IllegalArgumentException ex) { return ResponseEntity.badRequest().body(ex.getMessage()); }
}
②、全局异常处理(结合@ControllerAdvice
)
所有控制器抛出的SQLException
或DataAccessException
均会触发handleDatabaseError()
@ControllerAdvice
public class GlobalExceptionHandler { @ExceptionHandler({SQLException.class, DataAccessException.class}) public ResponseEntity<ErrorResponse> handleDatabaseError(Exception ex) { ErrorResponse error = new ErrorResponse(500, "数据库操作失败"); return ResponseEntity.internalServerError().body(error); } @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleDefault(Exception ex) { return ResponseEntity.status(500).body(new ErrorResponse(500, "系统错误")); }
}
③、
处理多个异常并返回视图
异常信息通过ModelAndView
传递到error.html
视图
@Controller
public class OrderController { @ExceptionHandler({OrderNotFoundException.class, PaymentFailedException.class}) public ModelAndView handleOrderErrors(Exception ex) { ModelAndView mav = new ModelAndView("error"); mav.addObject("message", ex.getMessage()); return mav; }
}
附录1:JDK原生注解
@Target
:指定注解的作用域(如类、方法、字段等)@Retention
:定义注解的生命周期(SOURCE/CLASS/RUNTIME)@Inherited
:允许子类继承父类的注解@Documented
:标记注解应被Javadoc工具记录@Repeatable
(Java 8+):支持重复注解声明
5、SpringMVC怎么设定重定向和转发的
在Spring MVC中,设定重定向和转发可以通过以下方法实现:
①、重定向(Redirect):
重定向是将客户端请求从一个URL重新引导到另一个URL的过程。在Spring MVC中,可以使用RedirectAttributes
对象来设置重定向参数。以下是一个示例:
在这个示例中,当用户访问/redirect
URL时,将被重定向到/anotherPage
URL,并显示一条消息。
@RequestMapping(value = "/redirect", method = RequestMethod.GET)
public String redirectToAnotherPage(RedirectAttributes redirectAttributes) {redirectAttributes.addFlashAttribute("message", "This is a redirect message.");return "redirect:/anotherPage";
}
②、转发(Forward):
转发是将客户端请求从一个控制器方法传递到另一个控制器方法的过程。在Spring MVC中,可以使用Model
对象来设置转发参数。以下是一个示例:
在这个示例中,当用户访问/forward
URL时,将被转发到/anotherPage
URL,并显示一条消息。
@RequestMapping(value = "/forward", method = RequestMethod.GET)
public String forwardToAnotherPage(Model model) {model.addAttribute("message", "This is a forward message.");return "forward:/anotherPage";
}
6、Spring和SpringMVC为什么需要父子容器?
首先,它们帮助划分功能边界,使得大型应用程序更易于管理。通过将不同模块或层次的组件分别放置在父子容器中,我们能够清晰地定义每个容器的职责,从而提高了代码的可维护性和可扩展性。
其次,父子容器在规范整体架构方面起到了关键作用。例如,我们可以将业务逻辑层(Service)和数据访问层(DAO)交给Spring管理,而将控制器层(Controller)交给SpringMVC管理。这种规范化有助于提高代码的可读性,并使团队协作更加顺畅。
此外,父子容器还能限制组件之间的依赖关系,确保模块之间的隔离。子容器可以访问父容器中的组件,但反之则不成立。这有助于减少意外的依赖和提高代码的稳定性。
另一个优势是方便切换子容器。如果我们需要更改子容器,例如从Spring MVC切换到Struts,只需更改子容器的配置而无需更改父容器。这提供了更好的可维护性和扩展性,使得应用程序更容易适应不同的技术栈或框架。
最后,父子容器的使用还有助于节省资源。父容器中的Bean可以在整个应用程序范围内共享,而不必每次创建。这对于大型应用程序来说尤为重要,可以降低内存和性能开销。
综上所述,父子容器在Spring框架中的应用不仅有助于功能性划分,还有助于架构的规范化、模块化、松耦合和可维护性。虽然一些现代框架如Spring Boot可能减少了对父子容器的需求,但在大型和复杂的应用程序中,父子容器仍然是一种有用的设计模式,有助于管理和组织应用程序的各个部分。
7、SpringMVC的拦截器和过滤器有什么区别?执行顺序?
拦截器和过滤器在Web应用中都扮演着请求和响应处理的角色,但它们之间存在一些关键区别。
①、首先,归属不同。拦截器是SpringMVC框架的一部分,而过滤器是Servlet规范的一部分。拦截器主要用于对控制器层的请求进行处理,它们提供了更细粒度的控制,可以在请求进入控制器之前和之后执行特定的逻辑,例如身份验证、日志记录和权限检查。过滤器独立于SpringMVC,用于处理通用的请求和响应内容,例如字符编码、压缩和安全性。
②、其次,执行顺序也不同。拦截器的执行顺序由配置文件中的顺序决定,可以有多个拦截器,它们按照配置的顺序依次执行。而过滤器的执行顺序由web.xml文件中的配置顺序决定,同样可以有多个过滤器,按照配置的顺序执行。一般来说,首先执行过滤器,然后再执行拦截器。
③、最后,用途不同。拦截器用于对SpringMVC的请求和响应进行特定的业务处理,通常与控制器层的请求处理有关。过滤器用于对所有Servlet请求和响应进行通用性的处理,通常关注请求和响应内容,而不涉及具体的业务逻辑。
总的来说,了解拦截器和过滤器之间的这些区别非常重要。在面试中,这种理解将有助于说明您在Web应用程序中如何处理请求和响应以及如何利用SpringMVC和Servlet规范的不同功能。
①、区别:
- 拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
- 拦截器只能对action请求(DispatcherServlet 映射的请求)起作用,而过滤器则可以对几乎所有的请求起作用。
- 拦截器可以访问容器中的Bean(DI),而过滤器不能访问(基于spring注册的过滤器也可以访问容器中的bean)。
②、执行顺序
过滤器 和 拦截器的触发时机也不同,我们看下边这张图。
过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。
③、主要区别
(1)拦截器是基于java的反射机制的,而过滤器是基于函数回调。
(2)拦截器不依赖于servlet容器,而过滤器依赖于servlet容器。
(3)拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
(4)拦截器可以访问action上下文、值栈里的对象,而过滤器不能。
(5)在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
8、Spring是如何保证 Controller并发的安全?
在Spring框架中,Controller层主要负责处理HTTP请求和生成响应。在高并发的环境下,Controller层可能会面临多种并发问题,如数据竞争、死锁、线程不安全等。Spring通过一系列策略和最佳实践来保证Controller的并发安全。以下是详细解释:
①. 无状态设计
Spring MVC中的Controller默认是单例的,这意味着所有请求共享一个Controller实例。为确保并发安全,Controller应设计为无状态的,即Controller中不包含任何可变的实例变量。所有请求在访问Controller时不会共享状态,因此不会出现线程安全问题。例如:
在这个例子中,result是局部变量,每个请求都有自己的局部变量空间,因此线程之间不会相互影响,从而保证了线程安全。
@Controller
public class SafeController {@GetMapping("/safe")@ResponseBodypublic String handleRequest(@RequestParam("input") String input) {// 使用局部变量,不存在线程安全问题String result = "Processed: " + input;return result;}
}
②. 避免使用可变的实例变量
由于Controller是单例的,任何可变的实例变量都会在并发访问时导致线程安全问题。因此,应避免在Controller中使用任何可变的实例变量,特别是像List、Map等集合类型。例如:
在这个例子中,counter是一个实例变量,会被多个请求共享访问。如果有多个线程同时访问handleRequest方法,可能会导致counter的值出现不一致。
@Controller
public class UnsafeController {private int counter = 0; // 非线程安全的实例变量@GetMapping("/unsafe")@ResponseBodypublic String handleRequest() {counter++; // 非线程安全操作return "Counter: " + counter;}
}
③. 使用ThreadLocal为线程提供独立的变量副本
如果确实需要在多个方法间共享一些数据,可以使用ThreadLocal。ThreadLocal能够为每个线程提供独立的变量副本,使数据在线程之间相互隔离,避免并发冲突。例如:
在这个例子中,ThreadLocal为每个线程提供独立的变量副本,每个请求的数据相互独立。注意在方法调用结束后调用threadLocal.remove()清理数据,以防止内存泄漏。
@Controller
public class ThreadLocalController {private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();@GetMapping("/threadlocal")@ResponseBodypublic String handleRequest(@RequestParam("input") String input) {threadLocal.set(input); // 每个线程独立的threadLocal副本try {return processInput();} finally {threadLocal.remove(); // 避免内存泄漏}}private String processInput() {return "Processed: " + threadLocal.get();}
}
④. 使用@Scope(“prototype”)将Controller设置为多例模式(不推荐)
虽然可以通过@Scope(“prototype”)将Controller作用域设置为多例,每次请求都会创建一个新的Controller实例,避免了线程安全问题,但不推荐这样做。因为它会增加内存和对象创建的开销,影响系统性能。